经过「百度搜索」来学习Jsonp,Promise,bind,apply,debounce

前言

最近在温习基础知识,如:jsonp,promise,bind,apply,debounce 等。那经过什么来测试练习了,就想到了「百度搜索」功能知足上面的测试。html

Jsonp

jsonp 主要是用来解决跨域问题的。应用很是普遍,关于更多的解决跨域方案请看前端面试总结之:js跨域问题 前端

jsonp的原理是什么了? 好比咱们定义一个函数foo,而后调用它git

// 定义
function foo() {
    console.log('foo')
}

// 调用
foo()
复制代码

那咱们将调用foo()的这段代码放在一个新建的js文件,好比a.js而后经过script标签引入a.jsgithub

// a.js
foo()
复制代码
function foo() {
    console.log('foo')
}

<script src="./a.js"></srcipt>
复制代码

jsonp原理与之相似:
咱们在本地定义好一个函数,如jsonp_1234565,而后将这个函数名经过特定标识符如cb=jsonp_1234565经过scriptsrc属性去请求一个js资源(一个get请求),即动态建立script标签。如:<script src="https://www.baidu.com?a=1&b=2&cb=jsonp_1234565"></script>后台经过cb这个特定标识符获得前端定义的函数名为jsonp_1234565而后将前端真正要的的数据放在jsonp_1234565的参数里,并将这个函数返回给前端如:jsonp_1234565({status: 0, data: {...}}) 代码以下面试

function jsonp({url = '', data = {}, cb='cb'} = {}) {
    if (!url) return
    // myPromise 请看下面实现,能够用成功回调的,由于学习特地用了Promise
    return myPromise((resolve, reject) => {
        const cbFn = `jsonp_${Date.now()}` // 定义函数名
        data[cb] = cbFn // 将函数名放在`cb`标识符里
        
        const oHead = document.querySelector('head')
        const oScript = document.create('script')
        
        const src = `${url}?${data2Url(data)}`
        oScript.src = src
        oHead.appendChild(oScript) // 将script标签插入head,以发送get请求
        
        // 定义函数,后台返回就调用
        window[cbFn] = function(res) {
            res ? resolve(res) : reject('error')
            // 若是不用Promise用回调的话只需在参数中加个success参数而后调用便可
            // success && success(res)
            
            oHead.removeChild(oScript) // 请求回来以后就没用了。若是不删除,每次请求以后就会建立一个script标签,致使页面不少的script标签,因此将它删除。用完了就扔,感受有点过河拆桥的意思
            window[cbFn] = null
        }
    })
}

function data2Url(data) {
    return Object.keys(data).reduce((acc, cur) => {
        acc.push(`${cur}=${data[cur]}`)
        return acc
    }, []).join('&')
}

// 能够先把myPromise改为原生的Promise测试百度搜索的接口
jsonp({
    url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
    data: {
        wd: 'a'
    },
    cb: 'cb'
}).then(res => {
    console.log(res)
})
复制代码

测试建议用 EGOIST开源的codepan.net/ 相似于JSBin/CodePen/JSFiddlejson

Promise

简易版的Promise实现,没有遵循A+规范,查看原文JavaScript Promises - Understand JavaScript Promises by Building a Simple Promise Example跨域

new Promise((resolve, reject) => {
    // 一系列操做 伪代码
    if (true) {
       resolve(res) 
    } else {
        reject(err)
    }
})
.then(fn1)
.then(fn2)
...
.catch(handleError)

复制代码

大体意思就是Promise这个类接受一个函数做为参数,这个参数函数又接受两个函数做为参数
new Promise 这个实例有thencatch这两个方法,thencatch又都接受函数做为参数,而且能够链式调用数组

核心思路就是定义个数组promiseChianFn用来装then的回调函数,then一次,就往promiseChianFn push一条then的回调函数,当在调用resolve函数的时候,就循环执行promiseChianFn的函数promise

class myPromise {
    constructor(excuteFn) {
        this.promiseChianFn = []
        this.handleError = () => {}
        // mybind 请看下面实现
        this._resolve = this._resolve.mybind(this)
        this._reject = this._reject.mybind(this)
        // 当即执行
        excuteFn(this._resolve, this._reject)
    }
    
    then(fn) {
        this.promiseChianFn.push(fn)
        
        return this // 原生Promise返回的是一个新的Promise
    }
    
    catch(handleError) {
        this.handleError = handleError
        
        return this
    }
    
    _resolve(val) {
        try {
            let storeVal = val
            // 循环执行,并把第一个函数执行的返回值赋值给storeVal 共下个函数接收 如:
           /** * .then(res => { * renturn 1 * }) * .then(res => { * console.log(res) // 1 * }) * */
            this.promiseChianFn.forEach(fn => {
                storeVal = fn(storeVal)
            })
        } catch(err) {
            this.promiseChianFn = []
            this._reject(err)
        }
    }
    
    _reject(err) {
        this.handleError(err)
    }
    
}

// 如今能够用myPromise 测试上面的jsonp了
复制代码

apply

callapply都是用来改变函数的上下文里面的this的,即改变this指向。
注意:上下文包含 VO(variable Object--变量对象),做用域链this这三个东西,具体请看js引擎的执行过程(一)性能优化

// 将 foo里面的上下文指向 ctx
foo.call(ctx, 1,2,3)
foo.apply(ctx, [1,2,3])
复制代码

callapply的原理就是方法借用
在知乎上面看到一篇文章的比喻 猫吃鱼,狗吃肉
那猫要吃肉,就借用狗吃肉的方法 即 狗.吃肉.call(猫)
那狗要吃鱼,就借用猫吃鱼的方法 即 猫.吃鱼.call(狗)

// fn.call(ctx) 既然是方法借用,那就给ctx添加一个该方法就能够了
Function.prototype.myapply = function(ctx, args = []) {
    const hash = Date.now() // 用时间戳是防止 ctx 上面的属性冲突
    ctx[hash] = this // 给 ctx 添加一个方法,this 就是 fn
    
    const res = ctx[hash](...args)
    delete ctx[hash] // 过河拆桥
    return res
}

// call 的话,只需将 args = [] 改成 ...args 便可

// 测试
const a = {
    name: 'a',
    getName() {
        console.log(this.name)
    }
}

const b = {
    name: 'b'
}

a.getName() // a
a.getName.myapply(b) // b
复制代码

bind

bind 返回一个新函数,并永久改变 this 指向,返回的新函数不管以后再怎么call,apply,bind都不会改变 this 指向了,并有偏函数的效果 若是要考虑 New的状况请参照MDN

// fn2 = fn.bind(ctx)
Function.prototype.mybind = function(ctx, ...args1) {
    const _this = this
    return function(...args2) {
        // 永远指向 ctx
        return _this.myapply(ctx, args1.concat(args2))
    }
}
// 测试
const fn = a.getName.mybind(b)
fn() // b
const fn2 = fn.bind(a)
fn2() // b
复制代码

debounce

「百度搜索」并无加入 debounce ,咱们能够给他加个 debounce 看下效果
debounce(防抖) 和 throttle(节流)主要是用来作性能优化
debounce 就像压弹簧,只要手不松开,弹簧就不会弹起来,常见应用场景就是input输入框,咱们在中止输入后才去作相关操做
throttle 就像拧紧水龙头,让水龙头隔一秒钟滴一滴水,常见应用场景为页面滚动优化

function debounce(cb, delay = 300) {
    let timer
    return function(...args) {
        timer && clearTimeout(timer)
        timer = setTimeout(() => {
            cb && cb.apply(this, args)
        }, delay)
    }
}
复制代码

接下来咱们模拟「百度搜索」加上debounce 想传 GIF 传不了, 请上 codepan.net/ 测试

所有代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <input type="text">
</body>
</html>
复制代码
function jsonp({url = '', data = {}, cb = 'cb'} = {}) {
  
    return new myPromise((resolve, reject) => {
        if (!url) return
        const cbFn = `jsonp_${Date.now()}`
        data[cb] = cbFn
        
        const oHead = document.querySelector('head') 
        const oScript = document.createElement('script')
        
        const src = `${url}?${data2Url(data)}`
        oScript.src = src
        
        oHead.appendChild(oScript)
        
        window[cbFn] = function(res) {
            resolve(res)
            oHead.removeChild(oScript)
            window[cbFn] = null
        }
    })
}

function data2Url(data) {
    return Object.keys(data).reduce((acc, cur) => {
        acc.push(`${cur}=${data[cur]}`)
        return acc
    }, []).join('&')
}

class myPromise {
    constructor(excuteFn) {
        this.promiseChainFn = []
        this.handleError = () => {}
        this._resolve = this._resolve.myBind(this)
        this._reject = this._reject.myBind(this)
        excuteFn(this._resolve, this._reject)
    }
  
    then(cb) {
        this.promiseChainFn.push(cb)
        return this
    }
  
    catch(handleError) {
        this.handleError = handleError
        return this
    }
  
    _resolve(res) {
        try {
            let storeVal = res
            this.promiseChainFn.forEach(fn => {
                storeVal = fn(storeVal)
            })
        } catch(e) {
            this.promiseChainFn = []
            this._reject(e)
        }
    }
  
    _reject(err) {
        return this.handleError(err)
    }
}

Function.prototype.myApply = function(ctx, args = []) {
  const hash = Date.now()
  ctx[hash] = this
  
  const res = ctx[hash](...args)
  delete ctx[hash]
  return res
}

Function.prototype.myBind = function(ctx, ...args1) {
  const _this = this
  return function(...args2) {
    return _this.myApply(ctx, args1.concat(args2))
  }
}

function debounce(cb, delay = 300) {
  let timer
  return function(...args) {
    timer && clearTimeout(timer)
    timer = setTimeout(() => {
      cb && cb.myApply(this, args)
    }, delay)
  }
}


const oInput = document.querySelector('input')

oInput.oninput = debounce(handleInput, 1000)

function handleInput(v) {
  jsonp({
    url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
    data: { wd: this.value},
    cb: 'cb'
  }).then(res => {
    console.log(res)
    return 1
  }).then(res => {
    console.log(res)
    return 2
  }).then(res => {
    console.log(res)
  }).catch(function a(err) {
    console.log(err)
  })
}
复制代码
相关文章
相关标签/搜索