1. 程式人生 > >深入理解javascript的bind

深入理解javascript的bind

bind有兩個功能

  • 實現this繫結
  • 實現柯里化

1.實現this繫結

function f(){
return this.a;
}
var g = f.bind({a : "test"});
console.log(g()); // test
var o = {a : 37, f : f, g : g};
console.log(o.f(), o.g()); // 37, test

第一個bind將{a:”test”}物件繫結到f函式的this上。使得f函式返回了this.a即返回了{a:”test”}的a屬性。

第二個呼叫o.g()時,g指標指向了var定義的g函式,由於g函式已經綁定了{a:”test”}物件,打印出test。

1.實現柯里化currying

什麼是柯里化捏?

在電腦科學中,柯里化(Currying)是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數且返回結果的新函式的技術。這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的,儘管它是 Moses Schnfinkel 和 Gottlob Frege 發明的。
var foo = {x: 888};
var bar = function () {
    console.log(this.x);
}.bind(foo);               // 繫結
bar(); // 888

下面是一個 bind 函式的模擬,testBind 建立並返回新的函式,在新的函式中將真正要執行業務的函式繫結到實參傳入的上下文,延遲執行了。

Function.prototype.testBind = function (scope) {
    var fn = this;                    //// this 指向的是呼叫 testBind 方法的一個函式, 
    return function () {
        return fn.apply(scope);
    }
};
var testBindBar = bar.testBind(foo);  // 繫結 foo,延遲執行
console.log(testBindBar); // Function (可見,bind之後返回的是一個延遲執行的新函式) testBindBar(); // 888

bind方法實現原始碼

先看一個案例

function foo() {
this.b = 100;
return this.a;
}
var func = foo.bind({a:1});
func(); // 1
new func(); // {b : 100}

new 一個func和直接執行func的區別是什麼呢?
new一個func之後,會建立一個空物件,空物件的prototype是foo的prototype,空物件的b屬性是100,會忽略掉return的a屬性,把當前物件返回。所以返回的就是新建的這個物件{b:100}了。

所以說,new一個新物件之後,對於bind,返回值被覆蓋了。

if (!Function.prototype.bind) {
//這裡 oThis和作為bind函式後的第一個引數傳入,上例中是{a:1}
Function.prototype.bind = function(oThis) {
//這裡的this是呼叫bind方法的物件,上例中是foo。
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('What is trying to be bound is not callable');
}
//通過arguments獲取多餘的引數,即第二個引數及以後的引數。bind函式將多餘的引數當作方法的形參
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
//fBound作為整個bind函式返回物件,上例中就是func物件
fBound = function() {
//這裡的this是bind返回的物件例項指向的this,上例中就是func呼叫時指向的this。
return fToBind.apply(this instanceof fNOP? this : oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
/*下兩句程式碼實現了object.create的作用。*/
//新建一個function 它的原型指向this的原型
fNOP.prototype = this.prototype;
//
fBound.prototype = new fNOP();
return fBound;
};
}

其中這段程式碼:

//這裡的this是bind返回的物件例項指向的this,上例中就是func呼叫時指向的this。
return fToBind.apply(this instanceof fNOP? this : oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};

1.按照例子,如果說直接呼叫

func()

那麼func 的this就指向了window物件,window物件肯定不在fNOP的原型鏈上,則返回oThis。即,指向var func = foo.bind({a:1});的{a:1}物件。

2.如果這樣呼叫

new func(); 

我們知道new方法返回一個新物件,新物件的prototype屬性指向構造器的prototype屬性,即指向foo的prototype屬性。

而在bind原始碼裡,fNOP.prototype = this.prototype;這裡this是指向的foo物件。那麼這裡的this也是指向的foo物件。那麼this instanceof fNOP返回true,就這麼成功的覆蓋了bind最原始的返回物件啦。