最近在溫習基礎知識,如:jsonp
,promise
,bind
,apply
,debounce
等。那經過什麼來測試練習了,就想到了「百度搜索」功能知足上面的測試。html
jsonp 主要是用來解決跨域問題的。應用很是普遍,關於更多的解決跨域方案請看前端面試總結之:js跨域問題 前端
那jsonp
的原理是什麼了? 好比咱們定義一個函數foo
,而後調用它git
// 定義
function foo() {
console.log('foo')
}
// 調用
foo()
複製代碼
那咱們將調用foo()
的這段代碼放在一個新建的js
文件,好比a.js
而後經過script
標籤引入a.js
了github
// a.js
foo()
複製代碼
function foo() {
console.log('foo')
}
<script src="./a.js"></srcipt>
複製代碼
jsonp
原理與之相似:
咱們在本地定義好一個函數,如jsonp_1234565
,而後將這個函數名經過特定標識符如cb=jsonp_1234565
經過script
的src
屬性去請求一個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
json
簡易版的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
這個實例有then
和catch
這兩個方法,then
和catch
又都接受函數做爲參數,而且能夠鏈式調用數組
核心思路就是定義個數組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了
複製代碼
call
和apply
都是用來改變函數的上下文裏面的this的,即改變this
指向。
注意:上下文包含 VO(variable Object--變量對象)
,做用域鏈
和this
這三個東西,具體請看js引擎的執行過程(一)性能優化
// 將 foo裏面的上下文指向 ctx
foo.call(ctx, 1,2,3)
foo.apply(ctx, [1,2,3])
複製代碼
call
和apply
的原理就是方法借用
在知乎上面看到一篇文章的比喻 貓吃魚,狗吃肉
那貓要吃肉,就借用狗吃肉的方法 即 狗.吃肉.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 返回一個新函數,並永久改變 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
(防抖) 和 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)
})
}
複製代碼