前言
最近在溫習基礎知識,如: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
通過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
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
這個例項有then
和catch
這兩個方法,then
和catch
又都接受函式作為引數,並且可以鏈式呼叫
核心思路就是定義個陣列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
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
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)
})
}
複製程式碼