實現一個bind函數

目前的打算仍是繼續深刻前端基礎知識,因此打算從polyfill開始作起。前端

bind函數

bind函數最多見的用法是綁定函數的上下文,好比在setTimeout中的this通常都是指向window,若是咱們想改變上下文,這裏可使用bind函數來實現。segmentfault

var a = 10;
var test = function() {
    console.log(this.a);
}
// 若是直接執行test,最終打印的是10.
var bindTest = test.bind({a: "111"})
bindTest(); // 111
複製代碼

從上面這個例子能夠看出來,bind函數改變了test函數中this的指向。 除此以外,bind函數還有兩個特殊的用法,一個是柯里化,一個是綁定構造函數無效。bash

柯里化

bind函數的柯里化實際上是不徹底的,其實只作了一次柯里化,看過MDN的polyfill實現後也就理解了。app

var test = function(b) {
    return this.a + b;
}
// 若是直接執行test,最終打印的是10.
var bindTest1 = test.bind({a: 20});
bindTest1(10); // 30
// 這裏的bind是個柯里化的函數
var bindTest2 = test.bind({a: 20}, 10);
bindTest2(); // 30;
複製代碼

構造函數無效

其實準確的來講,bind並非對構造函數無效,只是對new的時候無效,若是直接執行構造函數,那麼仍是有效的。函數

var a = 10;
var Test = function(a) {
    console.log(this.a);
}
var bindTest = Test.bind({a: 20});
bindTest(); // 20
// 在new的時候,Test中的this並無指向bind中的對象
new bindTest(); // undefined
複製代碼

實現一個bind

咱們能夠先實現一個簡易版本的bind,再不斷完善。因爲是在函數上調用bind,因此bind方法確定存在於Function.prototype上面,其次bind函數要有改變上下文的做用,咱們想想,怎麼才能改變上下文?沒錯,就是call和apply方法。ui

而後還要能夠柯里化,還好這裏只是簡單的柯里化,咱們只要在bind中返回一個新的函數,而且將先後兩次的參數收集起來就能夠作到了。this

Function.prototype.bind = function() {
    var args = arguments;
    // 獲取到新的上下文
    var context = args[0];
    // 保存當前的函數
    var func = this;
    // 獲取其餘的參數
    var thisArgs = Array.prototype.slice.call(args, 1);
    var returnFunc = function() {
        // 將兩次獲取到的參數合併
        Array.prototype.push.apply(thisArgs, arguments)
        // 使用apply改變上下文
        return func.apply(context, thisArgs);
    }
    return returnFunc;
}
複製代碼

這裏實現了一個簡單的bind函數,能夠支持簡單的柯里化,也能夠改變上下文做用域,可是在new一個構造函數的時候仍是會改變上下文。spa

這裏咱們須要考慮一下,怎麼作才能讓在new的時候無效,而其餘時候有效?.net

因此咱們須要在returnFunc裏面的apply第一個參數進行判斷,若是是用new調用構造函數的時候應該傳入函數自己,不然才應該傳入context,那麼該怎麼判斷是new調用呢?prototype

關於在new一個構造函數的時候,這中間作了什麼,建議參考這個問題:在js裏面當new了一個對象時,這中間發生了什麼?

因此咱們很容易得出,因爲最終返回的是returnFunc,因此最終是new的這個函數,而在new的過程當中,會執行一遍這個函數,因此這個過程當中returnFunc裏面的this指向new的時候建立的那個對象,而那個新對象指向returnFunc函數。

可是咱們但願調用後的結果只是new的func函數,和咱們正常new func同樣,因此這裏猜測,在returnFunc中,必定會將其this傳入func函數中執行,這樣才能知足這幾個條件。

Function.prototype.bind = function() {
    var args = arguments || [];
    var context = args[0];
    var func = this;
    var thisArgs = Array.prototype.slice.call(args, 1);
  	var returnFunc = function() {
      Array.prototype.push.apply(thisArgs, arguments);
      // 最關鍵的一步,this是new returnFunc中建立的那個新對象,此時將其傳給func函數,其實至關於作了new操做最後一步(執行構造函數)
      return func.apply(this instanceof returnFunc ? this : context, thisArgs);
    }
    return returnFunc
}
function foo(c) {
    this.b = 100;
    console.log(c);
    return this.a;
}

var func =  foo.bind({a:1});
var newFunc = new func() // undefined
複製代碼

可是這樣仍是不夠的,若是foo函數原型上面還有更多的方法和屬性,這裏的newFunc是無法獲取到的,由於foo.prototype不在newFunc的原型鏈上面。 因此這裏咱們須要作一些改動,因爲傳入apply的是returnFunc的一個實例(this),因此咱們應該讓returnFunc繼承func函數,最終版是這樣的。

Function.prototype.bind = function() {
    var args = arguments || [];
    var context = args[0];
    var func = this;
    var thisArgs = Array.prototype.slice.call(args, 1);
    var returnFunc = function() {
      Array.prototype.push.apply(thisArgs, arguments);
      // 最關鍵的一步,this是new returnFunc中建立的那個新對象,此時將其傳給func函數,其實至關於作了new操做最後一步(執行構造函數)
      return func.apply(this instanceof func ? this : context, thisArgs);
    }
    returnFunc.prototype = new func()
    return returnFunc
}
複製代碼

這樣咱們就完成了一個bind函數,這與MDN上面的polyfill實現方式大同小異,這裏能夠參考一下MDN的實現:Function.prototype.bind()

參考連接:

  1. MDN:Function.prototype.bind()

  2. 手寫bind()函數,理解MDN上的標準Polyfill

相關文章
相關標籤/搜索