前段時間有一場電話面試,記得裏面有個內容,問我http相關的東西,我內心暗爽,我把http圖解看了兩遍了,裏面的知識點啥的,基本都作了提煉和記錄,http?那還不是so easy?javascript
balabala講了一堆,從http到https到http2,還補充了點http3的東西,巴拉巴拉講了一堆,信心滿滿.誰知道一個問題就問住我了.java
面試官: http2實現了多路複用,http1.x爲何不能多路複用?
我: 我說由於http1.x要按照順序來.
面試官: 沒錯,可是爲何http1.x要按照順序來?
我: 唔...這個不知道..
面試官: HTTP/1.1是基於文本分割解析的協議,也沒有序號,若是多路複用會致使順序錯亂,http2則用幀的方式,等於切成一塊塊,每一塊都有對應的序號,因此能夠實現多路複用.es6
倒不是說這個問題怎麼樣怎麼樣,而是這個問題讓我意識到學習還不夠深刻,應該作到知其然並知其因此然.多思考多總結多提問,而後昨天恰好看到這樣的一道面試題就稍微總結一下面試
apply,call,bind都是js給函數內置的一些api,調用他們能夠爲函數指定this的執行,同時也能夠傳參.api
//apply
func.apply(thisArg, [argsArray])
//call
fun.call(thisArg, arg1, arg2, ...)
//bind
const newFun = fun.bind(thisArg, arg1, arg2, ...)
newFun()
複製代碼
apply和call就是傳參不同,可是兩個都是會在調用的時候同時執行調用的函數,可是bind則會返回一個綁定了this的函數.數組
咱們還須要知道一個事情,就是this的指向.bash
this的指向是在函數調用的時候肯定下來的,this的指向大體能夠分爲五種.app
默認綁定通常發生在回調函數,函數直接調用;函數
function test() {
//嚴格模式下是undefined
//非嚴格模式下是window
console.log(this);
}
setTimeout(function () {
//setTimeout的比較特殊
//嚴格模式和非嚴格模式下都是window
console.log(this);
});
arr.forEach(function () {
//嚴格模式下是undefined
//非嚴格模式下是window
console.log(this);
});
複製代碼
這個通俗點用一句話歸納就是誰調用就是指向誰學習
const obj = {
name:'joy',
getName(){
console.log(this); //obj
console.log(this.name); //joy
}
};
obj.getName();
複製代碼
const obj1 = {
name: 'joy',
getName() {
console.log(this);
console.log(this.name);
}
};
const obj2 = {
name: 'sam'
};
obj1.getName.call(obj2); //obj2 sam
obj1.getName.apply(obj2); //obj2 sam
const fn = obj1.getName.bind(obj2);
fn();//obj2 sam
複製代碼
function Vehicle() {
this.a = 2
console.log(this);
}
new Vehicle(); //this指向Vehicle這個new出來的對象
複製代碼
es6的箭頭函數比較特殊,箭頭函數this爲父做用域的this,不是調用時的this.要知道前四種方式,都是調用時肯定,也就是動態的,而箭頭函數的this指向是靜態的,聲明的時候就肯定了下來.比較符合js的詞法做用域吧
window.name = 'win';
const obj = {
name: 'joy',
age: 12,
getName: () => {
console.log(this); //其父做用域this是window,因此就是window
console.log(this.name); //win
},
getAge: function () {
//經過obj.getAge調用,這裏面this是指向obj
setTimeout(() => {
//因此這裏this也是指向obj 因此結果是12
console.log(this.age);
});
}
};
obj.getName();
obj.getAge();
複製代碼
既然有5種this的綁定方式,那麼確定有優先級的前後
箭頭函數 -> new綁定 -> 顯示綁定call/bind/apply -> 隱式綁定 -> 默認綁定
這裏直接給出告終論,有興趣的小夥伴們能夠本身去驗證一下
先來實現apply吧
Function.prototype.myApply = function (context, args) {
}
複製代碼
Function.prototype.myApply = function (context, args) {
//這裏默認不傳就是給window,也能夠用es6給參數設置默認參數
context = context || window
args = args ? args : []
}
複製代碼
Function.prototype.myApply = function (context, args) {
//這裏默認不傳就是給window,也能夠用es6給參數設置默認參數
context = context || window
args = args ? args : []
//給context新增一個獨一無二的屬性以避免覆蓋原有屬性
const key = Symbol()
context[key] = this
//經過隱式綁定的方式調用函數
context[key](...args)
}
複製代碼
Function.prototype.myApply = function (context, args) {
//這裏默認不傳就是給window,也能夠用es6給參數設置默認參數
context = context || window
args = args ? args : []
//給context新增一個獨一無二的屬性以避免覆蓋原有屬性
const key = Symbol()
context[key] = this
//經過隱式綁定的方式調用函數
const result = context[key](...args)
//刪除添加的屬性
delete context[key]
//返回函數調用的返回值
return result
}
複製代碼
這樣一個簡單的apply就實現了,可能會有一些邊界問題和錯誤判斷須要完善,這裏就不作繼續優化了
既然apply實現了,那麼call一樣也很是簡單了,主要就是傳參不同
這裏就直接上代碼吧
//傳遞參數從一個數組變成逐個傳參了,不用...擴展運算符的也能夠用arguments代替
Function.prototype.myCall = function (context, ...args) {
//這裏默認不傳就是給window,也能夠用es6給參數設置默認參數
context = context || window
args = args ? args : []
//給context新增一個獨一無二的屬性以避免覆蓋原有屬性
const key = Symbol()
context[key] = this
//經過隱式綁定的方式調用函數
const result = context[key](...args)
//刪除添加的屬性
delete context[key]
//返回函數調用的返回值
return result
}
複製代碼
bind和apply的區別在於,bind是返回一個綁定好的函數,apply是直接調用.其實想想實現也很簡單,就是返回一個函數,裏面執行了apply上述的操做而已.不過有一個須要判斷的點,由於返回新的函數,要考慮到使用new去調用,而且new的優先級比較高,因此須要判斷new的調用,還有一個特色就是bind調用的時候能夠傳參,調用以後生成的新的函數也能夠傳參,效果是同樣的,因此這一塊也要作處理 由於上面已經實現了apply,這裏就借用一下,實際上不借用就是把代碼copy過來
Function.prototype.myBind = function (context, ...args) {
const fn = this
args = args ? args : []
return function newFn(...newFnArgs) {
if (this instanceof newFn) {
return new fn(...args, ...newFnArgs)
}
return fn.apply(context, [...args,...newFnArgs])
}
}
複製代碼
以上全部實現能夠再加點判斷啊,例如調用的不是function就返回或者拋出錯誤啊之類的.我這裏就不處理了
以上就是apply,call,bind的實現了
一直在求學的道路上迷迷糊糊,磕磕碰碰,但願能變得愈來愈棒,早日晉升大牛.多思考,多總結,多練習.養成終身學習的習慣.加油吧