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

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

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

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

查看所有标签

猜你喜欢:

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

Designing for Emotion

Designing for Emotion

Aarron Walter / Happy Cog / 2011-10-18 / USD 18.00

Make your users fall in love with your site via the precepts packed into this brief, charming book by MailChimp user experience design lead Aarron Walter. From classic psychology to case studies, high......一起来看看 《Designing for Emotion》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具