bind函數polyfill源碼解析

準備知識

使用new來調用函數會自動執行下面的操做:javascript

  1. 建立一個全新的對象
  2. 這個新對象會被執行原型鏈接
  3. 這個新對象會綁定到函數調用的this
  4. 若是函數沒有返回其餘對象,那麼new表達式中的函數調用會自動返回這個新對象

注意this綁定規則,new操做具備最高的優先級java

《你不知道的JavaScript(上卷)》提供了一個例子,bar被硬綁定到obj上,可是new bar(3) 並無像咱們預計的那樣把obj.a修改成3。相反,new修改了硬綁定調用bar()中的this。由於使用了new綁定,咱們獲得了一個名字爲baz的新對象,而且baz.a的值爲3。bash

function foo(something) {
    this.a = something
}
var obj = {}
var bar = foo.bind(obj)
bar(2)
console.log(obj.a)  //2
var baz = new bar(3)
console.log(obj.a)  //2
console.log(baz.a)  //3
複製代碼

instanceof運算符的第一個變量是一個對象,暫時稱爲A;第二個變量通常是一個函數,暫時稱爲B。markdown

instanceof判斷準則:沿着A的__proto__這條線來找,同時沿着B的prototype這條線來找,若是兩條線能找到同一個引用,即同一個對象,那麼就返回true。app

源碼分析

MDN上提供的polyfill以下,主要的疑惑點應該就是 this instanceof fNOP 做用是什麼?函數

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
    }
    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP // 這段代碼會判斷硬綁定函數是不是被new調用,若是是的話就會使用新建立的this替換硬綁定的this
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)))
        }
    // 維護原型關係
    if (this.prototype) {
        // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype; } fBound.prototype = new fNOP() return fBound } } 複製代碼

this instanceof fNOP 單獨看是看不明白的,須要結合如下代碼才能說明它的做用源碼分析

if (this.prototype) {
    fNOP.prototype = this.prototype; 
}
fBound.prototype = new fNOP()
複製代碼

首先咱們要清楚:bind(...)會返回一個硬編碼的新函數,它會把參數設置爲this的上下文並調用原始函數。this

重點就是bind(...)返回的是一個函數!函數!函數!這意味着能夠對bind(...)返回的函數進行new操做,那麼問題就來了。編碼

this綁定中new操做具備最高的優先級,若是執行new操做,bind(...)應該不起做用,this應該指向new出來的新對象。spa

清楚了以上內容,咱們開始閱讀代碼。

if (typeof this !== 'function') {
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
    }
複製代碼

bind(...)必須由函數調用,因此以上代碼對this的類型進行檢查,若是不是函數類型則拋出錯誤。

var aArgs   = Array.prototype.slice.call(arguments, 1)
var fToBind = this
var fBound  = function() {
              return fToBind.apply(this instanceof fNOP // 這段代碼會判斷硬綁定函數是不是被new調用,若是是的話就會使用新建立的this替換硬綁定的this
                     ? this
                     : oThis,
                     aArgs.concat(Array.prototype.slice.call(arguments)))
           }
複製代碼

aArgs獲取傳入的其它參數,fToBind獲取須要硬綁定的函數,fBound爲返回的綁定操做函數,咱們先忽略fBound裏面的內容,繼續往下看。

if (this.prototype) {
    fNOP.prototype = this.prototype; 
}
fBound.prototype = new fNOP()
複製代碼

咱們假設對bind(...)返回的函數進行new操做(原型鏈以下),則this instanceof fNOP 爲true,此時咱們就知道執行了new操做,硬綁定的this不能生效,須要把this綁定到新生成的對象上。

若是沒有進行new操做的話,就用apply模擬bind綁定,一切按照原計劃進行。

最後咱們分析一下維護原型關係的重要性,例子以下:

function Foo(){
    console.log(this.a);
    this.a=1;
}
Foo.prototype.show=function() {console.log(this.a)};
Foo(); // undefined
var obj1=new Foo();
obj1.show();

var bar=Foo.bind({a:2});
bar(); // 2
var obj2=new bar();
obj2.show();
複製代碼

由於bind函數內部保持了原型關係的繼承,因此對象obj2才能訪問到原型上的show方法。

** 注意:Foo.show()是錯誤的,由於Foo的原型指向的是Function.prototype,只有Foo的實例才能調用show方法。

相關文章
相關標籤/搜索