call() 函數、apply() 函數、bind() 函數有一些巧妙的用法,能夠快速實現一些效果,這裡我們總結出了 5 個場景。
求陣列中的最大項和最小項
Array 陣列本身沒有 max() 函數和 min() 函數,無法直接獲取到最大值和最小值,但是 Math 卻有求最大值和最小值的 max() 函數和 min() 函數。
我們可以使用 apply() 函數來改變 Math.max() 函數和 Math.min() 函數的執行主體,然後將陣列作為引數傳遞給 Math.max() 函數和 Math.min() 函數。
var arr = [3, 5, 7, 2, 9, 11];
// 求陣列中的最大值
console.log(Math.max.apply(null, arr)); // 11
// 求陣列中的最小值
console.log(Math.min.apply(null, arr)); // 2
apply() 函數的第一個引數為 null,這是因為沒有物件去呼叫這個函數,我們只需要這個函數幫助我們運算,得到返回結果。
第二個引數是陣列本身,就是需要參與 max() 函數和 min() 函數運算的資料,運算結束後得到返回值,表示陣列的最大值和最小值。
類陣列物件轉換為陣列物件
函數的引數物件 arguments 是一個類陣列物件,自身不能直接呼叫陣列的方法,但是我們可以藉助 call() 函數,讓 arguments 物件呼叫陣列的 slice() 函數,從而得到一個真實的陣列,後面就能呼叫陣列的函數。
任意個數位的求和的程式碼如下所示:
// 任意個數位的求和
function sum() {
// 將傳遞的引數轉換為陣列
var arr = Array.prototype.slice.call(arguments);
// 呼叫陣列的reduce()函數
return arr.reduce(function (pre, cur) {
return pre + cur;
}, 0)
}
sum(1, 2); // 3
sum(1, 2, 3); // 6
sum(1, 2, 3, 4); // 10
用於繼承
// 父類別
function Animal(age) {
// 屬性
this.age = age;
// 範例函數
this.sleep = function () {
return this.name + '正在睡覺!';
}
}
// 子類
function Cat(name, age) {
// 使用call()函數實現繼承
Animal.call(this, age);
this.name = name || 'tom';
}
var cat = new Cat('tony', 11);
console.log(cat.sleep()); // tony正在睡覺!
console.log(cat.age); // 11
其中關鍵的語句是子類中的 Animal.call(this, age),在 call() 函數中傳遞 this,表示的是將 Animal 建構函式的執行主體轉換為 Cat 物件,從而在 Cat 物件的 this 上會增加 age 屬性和 sleep 函數,子類實際相當於如下程式碼:
function Cat(name, age) {
// 來源於對父類別的繼承
this.age = age;
this.sleep = function () {
return this.name + '正在睡覺!';
};
// Cat自身的範例屬性
this.name = name || 'tom';
}
執行匿名函數
假如存在這樣一個場景,有一個陣列,陣列中的每個元素是一個物件,物件是由不同的屬性構成,現在我們想要呼叫一個函數,輸出每個物件的各個屬性值。
我們可以通過一個匿名函數,在匿名函數的作用域內新增 print() 函數用於輸出物件的各個屬性值,然後通過 call() 函數將該 print() 函數的執行主體改變為陣列元素,這樣就可以達到目的了。
var animals = [
{species: 'Lion', name: 'King'},
{species: 'Whale', name: 'Fail'}
];
for (var i = 0; i < animals.length; i++) {
(function (i) {
this.print = function () {
console.log('#' + i + ' ' + this.species + ': ' + this.name);
};
this.print();
}).call(animals[i], i);
}
在上面的程式碼中,在 call() 函數中傳入 animals[i],這樣匿名函數內部的 this 就指向 animals[i],在呼叫 print() 函數時,this 也會指向 animals[i],從而能輸出 speices 屬性和 name 屬性。
bind()函數配合setTimeout
在預設情況下,使用 setTimeout() 函數時,this 關鍵字會指向全域性物件 window。當使用類的函數時,需要 this 參照類的範例,我們可能需要顯式地把 this 繫結到回撥函數以便繼續使用範例。
// 定義一個函數
function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// 定義一個原型函數
LateBloomer.prototype.bloom = function () {
// 在一秒後呼叫範例的declare()函數,很關鍵的一句
window.setTimeout(this.declare.bind(this), 1000);
};
// 定義原型上的declare()函數
LateBloomer.prototype.declare = function () {
console.log('I am a beautiful flower with ' + this.petalCount + ' petals!');
};
// 生成LateBloomer的範例
var flower = new LateBloomer();
flower.bloom(); // 1秒後,呼叫declare()函數
在上面的程式碼中,關鍵的語句在 bloom() 函數中,我們期望通過一個定時器,設定在 1 秒後,呼叫範例的 declare() 函數。很多人可能會寫出下面這樣的程式碼。
LateBloomer.prototype.bloom = function () {
window.setTimeout(this.declare, 1000);
};
此時,當我們呼叫 setTimeout() 函數時,由於其呼叫體是 window,因此在 setTimeout() 函數內部的 this 指向的是 window,而不是物件的範例。
這樣在 1 秒後呼叫 declare() 函數時,其中的 this 將無法存取到 petalCount 屬性,從而返回“undefined”,輸出結果如下所示:
I am a beautiful flower with undefined petals!
因此我們需要手動修改 this 的指向,而通過 bind() 函數能夠達到這個目的。
通過 bind() 函數傳入範例的 this 值,這樣在 setTimeout() 函數內部呼叫 declare() 函數時,declare() 函數中的 this 就會指向範例本身,從而就能存取到 petalCount屬性。
LateBloomer.prototype.bloom = function () {
window.setTimeout(this.declare.bind(this), 1000);
};
執行程式碼,在 1 秒後,得到的結果如下所示:
I am a beautiful flower with 4 petals!