理解 javascript 里的 bind() 函數
bind() 方法會創建一個新函數,當這個新函數被調用時,它的 this 值是傳遞給 bind() 的第一個參數, 它的參數是 bind() 的其他參數和其原本的參數。
語法是這樣樣子的:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
- thisArg 當綁定函數被調用時,該參數會作為原函數運行時的 this 指向。當使用 new 操作符調用綁定函數時,該參數無效。
- arg1, arg2, … (可選)當綁定函數被調用時,這些參數加上綁定函數本身的參數會按照順序作為原函數運行時的參數。
參數
bind 的第一個參數會作為原函數運行時的 this 指向,不多說;而第二個開始的參數是可選的,當綁定函數被調用時,這些參數加上綁定函數本身的參數會按照順序作為原函數運行時的參數。怎么理解?
function fn(a, b, c) {
return a + b + c;
}
var _fn = fn.bind(null, 10);
var ans = _fn(20, 30); // 60
fn 函數需要三個參數,_fn 函數將 10 作為默認的第一個參數,所以只需要傳入兩個參數即可,如果你不小心傳入了三個參數,放心,也只會取前兩個。
function fn(a, b, c) {
return a + b + c;
}
var _fn = fn.bind(null, 10);
var ans = _fn(20, 30, 40); // 60
這有啥用呢?如果某些函數,前幾個參數已經 “內定” 了,我們便可以用 bind 返回一個新的函數。也就是說,bind() 能使一個函數擁有預設的初始參數。這些參數(如果有的話)作為 bind() 的第二個參數跟在 this 后面,之后它們會被插入到目標函數的參數列表的開始位置,傳遞給綁定函數的參數會跟在它們的后面。
function list() {
return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);
var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
new
使用 bind 返回的結果還是個 function,是個 function 就可以被 new 運算符調用,那么結果呢?規范中說的很清楚了,當使用 new 操作符調用綁定函數時,bind 的第一個參數無效。
function Person(name, age) {
this.name = name;
this.age = age;
}
var _Person = Person.bind({});
var p = new _Person('hanzichi', 30); // Person {name: "hanzichi", age: 30}
一般我們不會去這么用,但是如果要寫個 bind 的 polyfill( http://caniuse.com/#search=bind ),還是需要考慮用 new 調用的情況。
我們也可以設置默認值(參考上一小節),原先提供的那些參數仍然會被前置到構造函數調用的前面。
function Person(name, age) {
this.name = name;
this.age = age;
}
var _Person = Person.bind(null, 'hanzichi');
var p = new _Person(30); // Person {name: "hanzichi", age: 30}
配合 setTimeout
什么時候容易丟失 this 指向?恩,setTimeout 是一個場景,很容易把 this 指向 window,當然,setInterval 也是一樣。當使用對象的方法時,需要 this 引用對象,你可能需要顯式地把 this 綁定到回調函數以便繼續使用對象。
var canvas = {
render: function() {
this.update();
this.draw();
},
update: function() {
// ...
},
draw: function() {
// ...
}
};
window.setInterval(canvas.render, 1000 / 60);
用 canvas 寫特效或者做游戲時經常會碰到類似的問題。上面的代碼是有問題的,render 方法中的 this 其實被指向了 window!我們可以用 bind, 顯式地把 this 綁定到回調函數以便繼續使用該對象。
window.setInterval(canvas.render.bind(canvas), 1000);
類似的情況還有 dom 的事件監聽,一不小心可能 this 就被指向了 dom 元素。可以參考下以前做 bigrender 時寫的這部分代碼 https://github.com/hanzichi/hanzichi.github.io/blob/master/2016/bigrender/js/bigrender.js#L179-L184 。
tip
bind 還能做一些有意思的事情。
通常來說,將一個類數組轉為數組,我們會用 slice(ie9- 不支持)。參考#14
var slice = Array.prototype.slice;
// slice.apply(arguments);
// slice(arguments, 1);
bind 能讓調用變的更加簡單。
// same as "slice" in the previous example
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);
// ...
slice(arguments);
// slice(arguments, 1);
再舉個類似的例子,比如說我們要添加事件到多個節點,for 循環當然沒有任何問題,我們還可以 “剽竊” forEach 方法:
Array.prototype.forEach.call(document.querySelectorAll('input[type="button"]'), function(el){
el.addEventListener('click', fn);
});
更進一步,我們可以用 bind 將函數封裝的更好:
var unboundForEach = Array.prototype.forEach
, forEach = Function.prototype.call.bind(unboundForEach);
forEach(document.querySelectorAll('input[type="button"]'), function (el) {
el.addEventListener('click', fn);
});
同樣類似的,我們可以將 x.y(z) 變成 y(x,z) 的形式:
var obj = {
num: 10,
getCount: function() {
return this.num;
}
};
var unboundBind = Function.prototype.bind
, bind = Function.prototype.call.bind(unboundBind);
var getCount = bind(obj.getCount, obj);
console.log(getCount()); // 10
再舉個栗子。每隔一秒在控制臺打印 1-5,看起來是道考察閉包的經典題目。
for(var i = 1; i <= 5; i++) {
!function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}(i);
}
ES6 下能用 let :
for(let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
也可以用 bind,瞬間逼格提升:
for(var i = 1; i <= 5; i++) {
setTimeout(console.log.bind(console, i), i * 1000);
}
來自:http://www.webhek.com/javascript-bind