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

栏目: Json · 发布时间: 3年前

内容简介:最近在温习基础知识,如:jsonp 主要是用来解决跨域问题的。应用非常广泛,关于更多的解决跨域方案请看那

最近在温习基础知识,如: 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)
  })
}
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

现代应用数学手册

现代应用数学手册

《现代应用数学手册》编委会 / 清华大学出版社 / 2005-1-1 / 48.00元

本书是进行科学计算的常备工具书,内容新颖,查阅方便,实用性强。主要介绍生产、科研、管理、数学等实践中在计算机上使用的各种计算方法和技巧。全书分为14章,依次为数值计算概论、插值法、函数逼近与曲线拟合、数值积分与数值微分、方程求根、线性方程组的直接解法和迭代解法、矩阵特征值问题、非线性方程组数值解与最优化方法、常微分方程初值问题和边值问题的数值解法、偏微分方程的数值解法、多重网络法和积分方程数值解法......一起来看看 《现代应用数学手册》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换