經過「百度搜索」來學習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)
  })
}
複製代碼
相關文章
相關標籤/搜索