JavaScript溫故而知新——bind()方法的實現

bind()方法和apply()call()類似,均可以用來改變某個函數運行時this的指向。bash

而且一樣接受的第一個參數做爲它運行時的this,以後的參數都會傳入做爲它的參數。
閉包

可是bind()還有一個最大的特色就是它會建立一個新的函數,以便於咱們稍後做調用,這也是它區別於apply()call()的地方。
app

先來看下bind的使用函數

var foo = {
    value: 1
};
function bar() {
    return this.value;
}
var bindFoo = bar.bind(foo);
console.log(bindFoo());     // 1
複製代碼

模擬實現一
post

Function.prototype.bind2 = function(context) {
    // 將this做保存,表明被綁定的函數
    var self = this;
    return function() {
        // 綁定函數可能會有返回值,因此這裏要return一下
        return self.apply(context);
    }
}
複製代碼

bind的傳參:須要注意咱們在bind的時候能夠進行傳參,而且在執行bind返回的函數的時候依然能夠傳參。以下例子:ui

var foo = {
    value: 1
};
function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);

}
var bindFoo = bar.bind(foo, 'xiao');
bindFoo('18');
// 1
// xiao
// 18
複製代碼

傳參效果的實現this

Function.prototype.bind2 = function (context) {

    var self = this;
    // 獲取 bind2 函數從第二個參數到最後一個參數
    var args = Array.prototype.slice.call(arguments, 1);

    return function () {
        // 這裏的arguments是指bind返回的函數傳入的參數
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(context, args.concat(bindArgs));
    }
}
複製代碼

bind還有一個特色:就是 bind 返回的函數能夠被做爲構造函數來使用,此時 bind 指定的this值會失效,但傳入的參數依然生效。以下例子:spa

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');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
複製代碼

能夠看到因爲這裏使用了new操做符,this已經指向了obj,所以this.value打印出來爲undefinedprototype

構造函數效果的實現
code

爲了讓this指向new出來的對象,咱們能夠經過修改返回的函數的原型來實現

Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        // 判斷是否做用構造函數
        // 看成爲構造函數時,將綁定函數的 this 指向 new 建立的實例,可讓實例得到來自綁定函數的值
        // 看成爲普通函數時,將綁定函數的 this 指向 context
        return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
    }
    // 修改返回函數的 prototype 爲綁定函數的 prototype,實例就能夠繼承綁定函數的原型中的值
    fBound.prototype = this.prototype;
    return fBound;
}
複製代碼

不過上面的寫法還存在點問題,fBound.prototype = this.prototype這一句代碼直接修改了 fBound.prototype,也會直接修改綁定函數的 prototype。以下例子:

function bar() {}

var bindFoo = bar.bind2(null);

// 修改 bindFoo 的值
bindFoo.prototype.value = 1;

// 致使 bar.prototype 的值也被修改了
console.log(bar.prototype.value)    // 1
複製代碼

所以能夠經過一個空函數做一箇中轉,避免綁定函數的 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);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }
    
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}
複製代碼

當調用bind的不是函數還得作一下錯誤處理,完整實現以下:

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      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);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

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

結尾

對於這篇文章理解起來有難度的話,建議先回顧一下原型,做用域和閉包相關的知識,能夠看看我以前的文章,這些知識點都是相互關聯的。連接在下方:

系列文章:

相關文章
相關標籤/搜索