JavaScript中的bind方法及其常見應用
一、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
JavaScript中的bind方法及其常見應用