在上一篇文章裏我們分析了ES5對幾個常用類新增的函數,今天就重點來講解一下Function中的bind函數。
簡單來說,bind函數用於將當前函數和指定對象綁定,返回一個新的函數,當新函數被調用時,代碼會在指定對象的上下文中執行。
這就涉及到JavaScript程序執行上下文Context的知識了,在JavaScript中函數內部如果存在與Context有關的代碼,如果我們在調用之前改變其Context,那麼執行結果就不同,這一點我們可以用一個最基本的例子來說明:
var name = 'Global';
var student = {
name: 'John'
};
var person = {
name: 'Scott',
getName: function() {
return this.name;
}
};
console.log(person.getName()); // Scott
var getName = person.getName;
console.log(getName()); // Global
console.log(getName.call(student)); // John
如上所示,我們在全局聲明一個name變量,然後聲明student和person對象,分別都有name屬性,其中person包含一個getName函數,用於返回所在對象的name屬性。第一步我們直接調用person的getName函數,返回Scott;然後我們先去到person的getName函數引用,之後直接調用,注意,這次調用跟第一步是不同的,它的執行環境是全局,所以函數內部的this.name會指向我們全局聲明的name,所以結果會返回Global;最後我們使用call方法將getName函數在student對象的上下文中執行,結果會返回student的name屬性,即John。
針對這個問題,我們可以使用bind函數將getName綁定person對象,返回一個帶有固定作用域的新函數,以後不管在哪調用這個新函數,都不用擔心作用域的問題:
var getName = person.getName.bind(person); //使用bind函數綁定person對象
console.log(getName()); //Scott
console.log(getName.call(student)); //Scott
注意最後一個調用雖然使用了call函數,但因爲getName使用bind綁定了person對象,所以不會再被call函數更改作用域了,打印結果仍然會是person對象的name。
另外一個例子是調用setTimeout或setInterval,當我們在函數內部調用setTimeout時需要特別小心,因爲setTimeout函數是在全局Context中執行的,我們來看下面這段代碼及運行的結果:
var name = 'John';
var person = {
name: 'Scott',
showMyName: function() {
setTimeout(this.printName, 1000);
},
printName: function() {
console.log('in person object, there is a name: ', this.name);
}
};
person.showMyName(); //in person object, there is a name: John
我們本希望在調用showMyName後延時1秒然後打印person的name屬性,可是結果並不是如我們期望的那樣,而是打印出了全局變量的值John,這是因爲setTimeout把person的printName函數放到了全局執行了,那printName裏面的this自然也指向了全局中的name變量。對於這個問題我們同樣可以使用bind函數將作用域綁定爲person對象,進而將printName的this總是指向person對象:
setTimeout(this.printName.bind(this), 1000); //使用bind綁定當前對象person
這樣一來結果就會如我們期望打印出person的name屬性,即Scott了:
上面介紹了這麼多,想必大家已經對bind的作用有所瞭解了,下面來詳細介紹一下bind函數的簽名:
func.bind(context, [arg1, [arg2, [...]]]);
函數的第一個參數是執行環境的上下文對象,後面的參數列表是預設參數,我們知道,調用bind函數會返回一個新函數,當這個新函數調用時,上面參數列表裏的預設函數也會附帶作爲實參傳入。在上面兩個例子中我們使用bind函數時並沒有預設參數,下面我們舉個例子來說明一下如何使用預設參數:
var listDrinks = function() {
var drinks = Array.prototype.slice.call(arguments);
console.log(drinks.join(', '));
};
listDrinks('tea', 'coffee'); //tea, coffee
var listDrinksOfRestaurant = listDrinks.bind(null, 'this restaurant serves: water');
listDrinksOfRestaurant('tea', 'coffee', 'milk'); //this restaurant serves: water, tea, coffee, milk
打印結果如下:
bind函數在ES5規範中是一個很重要的特性,給開發者帶來了極大的便利,但它在低版本的瀏覽器中是無法使用的,所以我們很有必要實現我們自己的bind函數:
if (!Function.prototpe.bind) {
Function.prototype.bind = function(context) {
var self = this,
args = Array.prototype.slice.call(arguments);
return function() {
return self.apply(context, args.slice(1).concat(arguments));
}
};
}
以上就是bind函數的全部內容,因爲它涉及到一些作用域的概念及運行機制,所以需要細細體會,瞭解其中的奧妙之處,謝謝大家。