前端面試之手寫一個bind方法

bind 函數對於寫react的人來說並不陌生。哦!是的,沒錯我的朋友,它的一個用處就是用來改變函數this指向的。如果細究一下bind的實現,發現裡面還是有不少東西的,我們今天展開討論一下。

在說bind之前呢,我們還要先來講講我們的老熟人this。


This的四種綁定規則

1.默認綁定

獨立函數調用時,this指向全局對象,如果使用嚴格模式,那麼全局對象無法使用默認綁定,this綁定至undefined並拋錯(TypeError: this is undefined)

2.隱式綁定

當函數作為引用屬性被添加到對象中,隱式綁定規則會把函數調用中的this綁定到這個上下文對象

3.顯示綁定

運用apply call 方法,在調用函數時候綁定this,也就是指定調用的函數的this值

4.new綁定

就是使用new操作符的時候的this綁定

上述四條規則優先級由上到下依次遞增。

由於js多樣的綁定規則,帶來了綁定隱式丟失問題,即函數中的this丟失綁定對象,即它會應用第 1 條的默認綁定規則,從而將this綁定到全局對象或者undefined上。

例如:綁定至上下文對象的函數被賦值給一個新的函數,然後調用這個新的函數時

前端面試之手寫一個bind方法

還記得我們當年我們是怎麼做的嗎?

...
var me = this;
return function () {
me.xxx()
}
...

還有就是用call 或者apply來顯示的綁定:

前端面試之手寫一個bind方法

由於這種用法太多了,所以呢ES5的時候給出了一個方法Function.prototype.bind(),自從有了它,前端工程師的生活似乎好過了很多,一個bind可以解決很多問題。


先看bind定義

MDN 給出的定義是:

bind()方法創建一個新的函數, 當這個新函數被調用時其this置為提供的值,其參數列表前幾項置為創建時指定的參數序列。

fun.bind(thisArg[, arg1[, arg2[, ...]]])

bind() 函數會創建一個新綁定函數,綁定函數與被調函數具有相同的函數體(在 ECMAScript 5 中)。調用綁定函數通常會導致執行包裝函數 綁定函數也可以使用new運算符構造:這樣做就好像已經構造了目標函數一樣。提供的this值將被忽略,而前置參數將提供給模擬函數

總的來說bind有如下三個功能點:

【1】改變原函數的 this 指向,即綁定上下文,返回原函數的拷貝

【2】當綁定函數被調用時,bind的額外參數將置於實參之前傳遞給被綁定的方法。

【3】注意,一個綁定函數也能使用new操作符創建對象,這種行為就像把原函數當成構造器,thisArg 參數無效。也就是 new 操作符修改 this 指向的優先級更高。


說了那麼多,我們現在可不可以實現一個bind呢?

其實有時候去研究這些JS API的實現還是蠻好玩的,你能學到很多知識。今天我們就手摸手寫一下吧。

從bind的定義描述中可以看到,我們要寫的這個函數的輸入輸出基本確定了:

輸入:接受一個或者多個參數,第一個是要綁定的上下文,額外參數當作綁定函數的前置參數。

輸出:返回原函數的拷貝,即返回一個函數,這個函數呢具備原函數的功能

前端面試之手寫一個bind方法

我們來測試一下:

前端面試之手寫一個bind方法

可以看到使用new實例化被綁定的方法,上下文還指向了傳入的obj,這個方法有點問題,我們需要考慮到的是在myBind的實現裡面,需要檢測new的操作


我們先考慮一下new操作符在調用構造函數時做了哪些操作?

比如說 var a = new b()

  1. 創建一個新的對象 newObj{}
  2. 繼承被實例化函數的原型 :newObj.__proto__ = b.prototype
  3. 將這個對象newObj綁定到構造函數b中的 this
  4. 如果沒有返回其他對象,new 操作符調用的函數則會返回這個對象newObj

所以我們做了如下修改:

前端面試之手寫一個bind方法

測試OK了:

前端面試之手寫一個bind方法

這個用例中我們來討論一下

bar的this指向:

變量 bar 是綁定之後的函數,也就是 fnBound, _self 是原函數 foo 的引用

如果直接bar('jack') 它指向window 或undefined。

new bar('alice') (相當於 new foo('alice'))過程中,fnBound的this指向了new表達式返回的對象alice

如果是 new 調用綁定函數,此時綁定函數中的 this 是由 new 調用綁定函數返回的實例對象,這個對象的構造函數是 fnBound

當我們忽略掉原型連接那行代碼時,其原型對象並不等於原函數 self 的原型,所以 this instanceof self ? this : oThis 得到的值還是指定的傳入的對象,而不是 new 返回的對象

所以fnBound.prototype = this.prototype 是有必要的,!!但注意 這個原型賦值是有問題的:

原因我在這裡先不說了留給各位討論啦。

前端面試之手寫一個bind方法

這裡我們創建了一個空函數來做中間人,承接原函數的原型丟給綁定函數,好了問題就搞完了,是不是很多知識點,慢慢看吧,我去踢球去了。


分享到:


相關文章: