深入JavaScript 模擬實現bind

bind

bind() 方法會創建一個新函數。當這個新函數被調用時,bind() 的第一個參數將作爲它運行時的 this,之後的一序列參數將會在傳遞的實參前傳入作爲它的參數。

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

  • 返回一個函數
  • 可以傳入參數

例1

var foo = { value: 1};

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

var bindFoo = bar.bind(foo); 
bindFoo(); // 1

讓我們看看發生了什麼?
(1) bind返回了一個函數
(2) 傳入的第一個參數改變了bar的指針

模擬bind第一版:

Function.prototype.myBind = function (context) {
    var self = this;
    return function () {
        self.apply(context);
    }
}

例2

var foo = {value: 1};

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

var bindFoo = bar.bind(foo, 'bty', '18');
bindFoo();   // 1  bty  18

var bindFoo = bar.bind(foo, 'bty');
bindFoo('18');   // 1  bty  18

var bindFoo = bar.bind(foo);
bindFoo('bty', '18');   // 1  bty  18

讓我們看看發生了什麼?

name和age兩個參數,
(1)可以都在bind的時候傳入
(2)也可以在 bind 的時候,只傳一個 name,在執行返回的函數的時候,再傳另一個參數 age
(3)還可以在返回函數的時候都傳入

這裏我們用 arguments 進行處理,

模擬bind第二版:

Function.prototype.myBind = function (context) {
    var self = this;
    // 獲取myBind從第二個參數到最後一個參數
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
       // 這時的arguments是指bind返回的函數傳入的參數
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(context, args.concat(bindArgs));
    }
}

例3

bind 另一個特點:
一個綁定函數也能使用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 = 'bty';

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

var obj = new bindFoo('18'); // undefined dadd  18
console.log(obj.habit);  // shopping
console.log(obj.friend);  //bty

讓我們看看發生了什麼?
全局和 foo 中都聲明瞭 value 值,但最後返回了 undefind,說明綁定的 this 失效了。
如果大家瞭解 new 的模擬實現,就會知道這個時候的 this 已經指向了 obj。

模擬bind第三版:

Function.prototype.myBind = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fbound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        // 當作爲構造函數時,this 指向實例,self 指向綁定函數,因爲下面一句 `fbound.prototype = this.prototype;`,已經修改了 fbound.prototype 爲 綁定函數的 prototype,此時結果爲 true,當結果爲 true 的時候,this 指向實例。
        // 當作爲普通函數時,this 指向 window,self 指向綁定函數,此時結果爲 false,當結果爲 false 的時候,this 指向綁定的 context。
        self.apply(this instanceof self ? this : context, args.concat(bindArgs));
    }
    fbound.prototype = this.prototype;
    return fbound;
}

上面代碼的問題:
(1)fbound.prototype = this.prototype:我們直接修改 fbound.prototype 時,也會直接修改函數的 prototype。這個時候,我們可以通過一個空函數來進行中轉
(2)如果調用bind不是函數怎麼辦?

模擬bind第四版:

Function.prototype.myBind = function (context) {
    if (typeof this !== "function") {      //(2)
       throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    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.prototype = this.prototype;
    fbound.prototype = new fNOP();   //(1)
    return fbound;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章