前端面試題:用 JS 來(lái)實(shí)現(xiàn)內(nèi)置的 Bind 方法
大家好,我是前端西瓜哥,今天我們用 JS 來(lái)實(shí)現(xiàn)內(nèi)置的 bind 方法。
bind 的用法
在實(shí)現(xiàn)之前,我們先學(xué)習(xí)一下 Function.prototype.bind 的用法。
function.bind(thisArg[, arg1[, arg2[, ]]])
bind 是函數(shù)特有的一個(gè)方法,可以創(chuàng)建一個(gè)綁定了 this 的新函數(shù)。
接受的參數(shù)為如下。
- 第 1 個(gè)參數(shù) thisArg:用于修改 this 指向,且 this 一旦修改后將無(wú)法再改變。
- arg1, arg2, ...:剩余的是可選的參數(shù)項(xiàng),會(huì)在 bind 返回的新函數(shù)調(diào)用時(shí),會(huì)作為函數(shù)的前幾個(gè)參數(shù)去調(diào)用。
this 的指向問(wèn)題
我們?cè)陂_發(fā)的時(shí)候,有時(shí)候會(huì)遇到 JS 的 this 指向丟失問(wèn)題。
下面我們看一個(gè)例子。
const person = {
nickname: '前端西瓜哥',
eatWatermelon() {
console.log(this.nickname + ' 吃西瓜');
}
};
person.eatWatermelon();
上面的代碼中,在調(diào)用 person.eatWatermelon 時(shí),this 指向 person,輸入結(jié)果為 前端西瓜哥 吃西瓜。
下面我們?cè)賵?zhí)行下面代碼。
const eatWatermelon = person.eatWatermelon;
eatWatermelon();
輸入結(jié)果就匪夷所思了起來(lái),它是:undefined 吃西瓜。
這是因?yàn)?this 的指向變成了 eatWatermelon() 執(zhí)行時(shí)所在作用域的 this,在瀏覽器 script 標(biāo)簽最外層時(shí),是全局對(duì)象 window(嚴(yán)格模式下,全局對(duì)象 this 會(huì)變成 undefined)。
所以 eatWatermelon() 執(zhí)行中的 this.nickname 等價(jià)于 window.nickname,因?yàn)槲覀儧](méi)有賦值過(guò),所以是 undefined。
有時(shí)候我們不希望 this 丟失,該怎么辦?
這時(shí)候我們就要用到一個(gè) bind 方法,可以永久改變 this 的指向,且不能再改變。
const eatWatermelon = person.eatWatermelon.bind(person);
eatWatermelon();
這樣的話,eatWatermelon 函數(shù)的 this 就會(huì)永遠(yuǎn)指向 person,能輸出我們預(yù)期的 前端西瓜哥 吃西瓜。
所以,對(duì)于一個(gè)函數(shù)來(lái)說(shuō),它的 this 指向是在執(zhí)行時(shí)確定的:
- 如果函數(shù)是 bind 返回的,this 永遠(yuǎn)指向執(zhí)行 bind 綁定的那個(gè) thisArg 值。
- 如果函數(shù)前面有個(gè)對(duì)象,那 this 指向這個(gè)對(duì)象。
- 如果函數(shù)前沒(méi)有對(duì)象,那 this 指向當(dāng)前的作用域(可能是函數(shù)作用域,可能是全局作用域)。
另一種控制 this 指向的寫法是使用 箭頭函數(shù),尤其適合在函數(shù)中調(diào)用另一個(gè)函數(shù)的情況,因?yàn)槠蜻@里不展開講。
積累參數(shù)
bind 除了常用于強(qiáng)綁 this 外,另一個(gè)用的比較少的作用:預(yù)置函數(shù)參數(shù)。
function add(a, b, c) {
return a + b + c;
}
const addSix = add.bind(null, 6);
const addSixThenAddFour = addSix.bind(null, 4);
addSixThenAddFour(5)
// 15
addSixThenAddFour(7)
// 17
實(shí)現(xiàn)一個(gè) bind
下面進(jìn)入正題,實(shí)現(xiàn)一個(gè) bind。
Function.prototype.myBind = function(thisArg, prefixArgs) {
const fn = this;
return function(args) {
return fn.call(thisArg, prefixArgs, args);
}
}
要點(diǎn)是利用 閉包。
讓返回的新函數(shù)可以訪問(wèn)到三個(gè)私有屬性:
- fn(原來(lái)的函數(shù))。
- thisArg(需要強(qiáng)綁不變的 this 指向)。
- prefixArgs 屬性。
當(dāng)我們調(diào)用這個(gè)新函數(shù)時(shí),我們會(huì)執(zhí)行 fn 函數(shù),并利用 call 方法來(lái)指定 this 為 thisArg,然后將預(yù)填充的多個(gè)參數(shù),和新函數(shù)接收的參數(shù)依次填入。
最后不要忘記返回調(diào)用后的值。因?yàn)樾潞瘮?shù)是原函數(shù)的封裝,返回值也要和原函數(shù)表現(xiàn)一致。
結(jié)尾
bind 方法的實(shí)現(xiàn)并不復(fù)雜,更重要的是你要先掌握好 bind 的用法。
就好比做業(yè)務(wù)需求一樣,不明確需求,就容易產(chǎn)生 bug,
然后需要你對(duì)閉包有一定的認(rèn)識(shí),知道如何去保存私有變量,以及封裝函數(shù)的寫法(記得 return 原函數(shù)的返回值)。