JavaScript中的bind方法及其常見應用

weixin_33982670發表於2018-03-10

一、bind()方法的實現

  在JavaScript中,方法往往涉及到上下文,也就是this,因此往往不能直接引用。就拿最常見的console.log("info…")來說,避免書寫冗長的console,直接用log("info…")代替,不假思索的會想到如下語法:

var log = console.log;
log("info");

  很遺憾,執行報錯:TypeError: Illegal invocation。

  原因很清楚:對於console.log("info…")而言,log方法在console物件上呼叫,因此log方法中的this指向console物件,而我們用log變數指向console.log方法,然後直接呼叫log方法,此時log方法的this指向的是window物件,上下文不一致,當然會報錯了。

  此時我們可以用bind方法解決這個問題。bind方法允許手動傳入一個this,作為當前方法的上下文,然後返回持有上下文的方法。例如:

var write = document.write.bind(document);
write("hello");

  這樣就不會報錯了。但是,bind方法並不支援ie 8以及更低版本的瀏覽器,我們完全可以自己實現一個,很簡單。

Function.prototype.bind = Function.prototype.bind || function(context){ 
    var _this = this;   
    return function(){ 
        _this.apply(context, arguments); 
    }; 
};

  核心就是通過apply方法實現,閉包的經典應用。_this指向當前方法,context指向當前方法的上下文,二者均通過閉包訪問。

  bind所做的就是自動封裝函式在函式自己的閉包中,這樣我們可以捆綁上下文(this關鍵字)和一系列引數到原來的函式。你最終得到的是另一個函式指標。

function add(a,b){
    return a + b;
}
var newFoo = add.bind(this,3,4);

  請注意,我們不僅捆綁this到newFoo()函式,而且我們也捆綁了兩個引數。所以,當我們呼叫newFoo()的時候,返回值將是7。但是,如果我們在呼叫之前newFoo更改的引數的話,會發生什麼?

  如果我們使用變數繫結引數到newFoo(),然後在呼叫newFoo()前改變變數,你覺得值會變為什麼呢?

function add(a,b){
    return a + b;
}
var a = 3;
var b = 4;
var newFoo = add.bind(this,a, b);
a = 6;
b = 7;
console.log(newFoo());

  返回值仍然是7,因為bind()繫結的是引數的值,而不是實際變數的值。這是好訊息,我們可以在程式碼中利用這個巨大的優勢。

二、bind()的應用:

1、繫結函式的this值

  bind()最簡單的用法是建立一個函式,使這個函式不論怎麼呼叫都有同樣的this值。常見的錯誤就像上面的例子一樣,將方法從物件中拿出來,然後呼叫,並且希望this指向原來的物件。如果不做特殊處理,一般會丟失原來的物件。使用bind()方法能夠很漂亮的解決這個問題:

this.num = 9; 
var mymodule = {
  num: 81,
  getNum: function() { return this.num; }
};

module.getNum(); // 81

var getNum = module.getNum;
getNum(); // 9, 因為在這個例子中,"this"指向全域性物件

// 建立一個'this'繫結到module的函式
var boundGetNum = getNum.bind(module);
boundGetNum(); // 81

  改變物件方法裡this的值

  改變事件處理函式裡的this值,因為在事件處理函式中的this指向的是dom元素,在某些情況下我們需要改變這個this值

2、偏函式實現

  擷取一段關於偏函式的定義:

Partial application can be described as taking a function that accepts some number of arguments, 
binding values to one or more of those arguments,

and returning a new function that only accepts the remaining, un-bound arguments.

  這是一個很好的特性,使用bind()我們可以設定函式的預定義引數,然後呼叫的時候傳入其他引數即可。

//使用bind,我們就可以像這樣寫程式碼實現Currying:
function add(a,b,c) {
  return a+b+c;
}
var addAgain = add.bind(this, 1, 2);
var result = addAgain(3);
function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// 預定義引數37
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

3、在定時器中使用,比如和setTimeout一起使用

  一般情況下setTimeout()的this指向window或global物件。當使用類的方法時需要this指向類例項,就可以使用bind()將this繫結到回撥函式來管理例項。

4、bind用於事件處理程式

  當一個事件處理程式被呼叫時,它訪問的上下文會生成事件,而不是在建立事件處理程式的物件中。通過使用bind,可以肯定的是,函式會被訪問正確的上下文。

三、多次繫結bind方法

  如果使用bind()方法多次繫結,最終得到的this會是哪個繫結的呢?
function say() {  
    alert(this.x);  
};  
var a = say.bind({x: 1});  
var b = a.bind({x: 2});  
b(); // 這裡會輸出1還是2呢?

  那麼我們不妨分析一下:

//say函式使用bind方法,穿進去了一個物件,相當於
var a = function() {  
    return say.apply({x: 1});  
};  

//如果我們對得到的函式a再進行繫結,則相當於
var b = function() {  
    return a.apply({x: 2});  
};  
即
var b = function() {  
    return function() {  
        return say.apply({x: 1});  
    }.apply({x: 2});  
};  
  這樣雖然我們改變了函式a裡this的值,但是最後函式say裡的this的值還是由第一次繫結時的引數決定,而與函式a中的this值無關。

1、多次繫結的結果

  所以無論使用bind繫結多少次,最終原函式的this值是由第一次繫結傳的引數決定的

 2、多次繫結引數的順序

function say() {  
    alert(this.x);  
};  
var a = say.bind({x: 1},1,2,3);  
var b = a.bind({x: 2},4,5,6);  
a(7,8,9);  
b(7,8,9);   
// 此時原函式say引數的順序的怎樣的呢?  
// 是[4,5,6,1,2,3,7,8,9]還是[1,2,3,4,5,6,7,8,9] 

  首先對say使用bind方法,會改變函式say的this值,和“內建”引數。所以 a(7,8,9) 的引數組成是:內建的引數 + 呼叫時傳入的引數 = 最終函式,即[1,2,3]+ [7,8,9] = [1,2,3,7,8,9]

  而對函式a使用bind方法,只會改變函式a的this值,和往函式a裡“內建”引數。所以 b(7,8,9) 的引數組成是:[1,2,3](在函式say內建的引數) + [4,5,6](在函式a內建的引數) + [7,8,9] = [1,2,3,4,5,6,7,8,9]
  總結:對哪個函式使用bind()方法即改變這個函式的this值,和內建其引數,或者說像克里化一樣理解,先預置好引數
var a = say.bind({x:1},1,2,3); // 是改變函式say的this值,和在函式say上預置引數1,2,3  
var b = a.bind({x: 2}, 4,5,6); // 是改變函式a的this,和在函式a上預置預置引數4,5,6 

 

相關文章