循序漸進的用js實現一個bind()

thomaszhou發表於2018-03-28

如果對call,apply,bind的應用和區別還不瞭解,可以去看我之前的文章瞭解下。 讓你弄懂 call、apply、bind的應用和區別

循序漸進的用js實現一個bind()

如果出現錯誤,請在評論中指出,我也好自己糾正自己的錯誤

author: thomaszhou

bind實現

一般我們會直接使用bind函式,但是這次我們通過原生js來嘗試實現這個函式

bind() 方法會建立一個新函式。當這個新函式被呼叫時,bind() 的第一個引數將作為它執行時的 this,之後的一序列引數將會在傳遞的實參前傳入作為它的引數

由此我們可以首先得出 bind 函式的三個特點:

  • (1)返回一個函式:我們可以使用 call 或者 apply 實現
  • (2)可以傳入引數:我們用 arguments 進行處理
var foo = { value: 1 };

function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
}

var bindFoo = bar.bind(foo, 'daisy'); // (1)bindFoo是一個函式
bindFoo('18'); // (2)此處可以再次傳入引數
// 1
// daisy
// 18
複製程式碼

先實現前兩個特點:實現程式碼(version 1.0):

Function.prototype.bind2 = function (context) {
    var self = this;
    // 獲取bind2函式從第二個引數到最後一個引數
    var args = Array.prototype.slice.call(arguments, 1);
    return function () {
        // 這個時候的arguments是指bind返回的函式bindFoo呼叫時傳入的引數
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(context, args.concat(bindArgs));
    }
}
複製程式碼
  • (3)一個繫結函式也能使用new操作符建立物件:這種行為就像把原函式當成構造器。提供的 this 值被忽略,同時呼叫時的引數被提供給模擬函式。: 通過修改返回的函式的原型來實現
var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18'); // this 已經指向了 obj
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
複製程式碼

注意:儘管在全域性和 foo 中都宣告瞭 value 值,最後依然返回了 undefind,說明繫結的 this 失效了

先實現第三個特點:實現程式碼(version 2.0):

Function.prototype.bind2 = function (context) {
    let self = this;
//    self --> ƒ bar(){}
    let args = Array.prototype.slice.call(arguments, 1);
    let fbound = function () {
      let bindArgs = Array.prototype.slice.call(arguments); 
       // (1) 當作為建構函式時,this --> 例項(fbound建立的的例項),self --> 繫結函式bar,結果為true,那麼self指向例項
      // (2) 當作為普通函式時,this -->window,self -->繫結函式,此時結果為false,那麼 self指向繫結的 context。
      self.apply(this instanceof self ? this : context, args.concat(bindArgs));
    };
   // 為了要讓 this instanceof self為true,就要使建立的例項繼承繫結函式bar,
   // 唯一辦法就是讓建立例項的函式fbound的prototype指向bar函式的prototype
    fbound.prototype = this.prototype;
    return fbound;
};
複製程式碼

優化:實現程式碼(version 3.0):

version 2.0 直接將 fbound.prototype = this.prototype,我們直接修改fbound.prototype 的時候,也會直接修改函式bar的 prototype。這個時候,我們可以通過一個空函式來進行中轉,然後利用組合繼承的方式來實現

Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    
    var fNOP = function () {}; // // 定義一箇中間函式,用於作為繼承的中間值

    var fbound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(this instanceof self ? this : context, args.concat(bindArgs));
    }
    // 先讓 fNOP 的原型方法指向 this 即函式bar的原型方法,繼承 this 的屬性
    fNOP.prototype = this.prototype;
    // 再將 fbound 即要返回的新函式的原型方法指向 fNOP 的例項化物件
    // 這樣,既能讓 fBound 繼承 this 的屬性,在修改其原型鏈時,又不會影響到 this 的原型鏈
    fbound.prototype = new fNOP();
    return fbound;
}
複製程式碼

在上面的程式碼中,我們引入了一個新的函式 fun,用於繼承原函式的原型,並通過 new 操作符例項化出它的例項物件,供 fBound 的原型繼承,至此,我們既讓新函式繼承了原函式的所有屬性與方法,又保證了不會因為其對原型鏈的操作影響到原函式

-------------優化三點-------------------------

  • (相容性)當 Function 的原型鏈上沒有 bind 函式時,才加上此函式
Function.prototype.bind = Function.prototype.bind || function () {
    ……
};
複製程式碼
  • 只有函式才能呼叫 bind 函式,其他的物件不行。即判斷 this 是否為函式。
if (typeof this !== 'function') {
    throw new TypeError("NOT_A_FUNCTION -- this is not callable");
}
複製程式碼
  • 對於引數傳遞的程式碼,可以用ES6的擴充運算子來替換

最終版本!!!

Function.prototype.bind = Function.prototype.bind || function (context, ...formerArgs) {
    let self = this;

    if (typeof this !== 'function') {
			throw new TypeError("NOT_A_FUNCTION -- this is not callable");
	}
    let fNOP = function () {}; 

    let fbound = function (...laterArgs) {
      self.apply(this instanceof self ? this : context, formerArgs.concat(laterArgs)); 
      // es6 : formerArgs.concat(laterArgs)
    };
   
    fNOP.prototype = this.prototype;
    
    fbound.prototype = new fNOP();
    return fbound;
  };

複製程式碼

相關文章