阅读 414

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

前言

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

Jsonp

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

jsonp的原理是什么了? 比如我们定义一个函数foo,然后调用它

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

// 调用
foo()
复制代码

那我们将调用foo()的这段代码放在一个新建的js文件,比如a.js然后通过script标签引入a.js

// 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/JSFiddle

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的函数

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)
  })
}
复制代码
关注下面的标签,发现更多相似文章
评论