原文連結:How to use the apply(?), call(?), and bind(➰) methods in JavaScript
原文作者:Ashay Mandwarya
譯者:JintNiu
推薦理由:apply
,call
和bind
在面試和日常編碼中常會遇到,瞭解和掌握他們的用法變得尤為重要。
在本文中,我們將討論函式原型鏈中的 apply
,call
和 bind
方法,它們是 JavaScript
中最重要且經常使用的概念,且與 this
關鍵字密切相關。
因此,想要掌握本文所述內容,您必須熟悉 this
關鍵字的概念和用法。可以參考這篇文章。
譯者注:可參考:[譯] JavaScript 之 this 指南
要了解 apply
|call
|bind
,我們首先需要了解 JavaScript
中的 Function
,當然這前提是你可以熟料運用 this
了。
Functions
Function
建構函式建立了一個新的 Function
物件。直接呼叫建構函式的話,可以動態地建立出在全域性作用域下的函式。
在 JavaScript
中,函式是一種物件,可以由apply
,call
和 bind
方法進行呼叫。
要檢查函式是否為一個 Function
物件,我們可以使用以下程式碼進行判斷,該程式碼段返回 true
。
(function () { }).constructor === Function ? console.log(true) : console.log(false);
複製程式碼
全域性
Function
物件沒有自己的方法或屬性。但由於它本身就是一個函式,可以通過Function.prototype
原型鏈繼承一些方法和屬性。 — MDN
以下是函式原型鏈中的方法:
Function.prototype.apply()
Function.prototype.bind()
Function.prototype.call()
Function.prototype.isGenerator()
Function.prototype.toSource()
Object.prototype.toSource
Function.prototype.toString()
Object.prototype.toString
這篇文章中我們只討論前三個。
Apply ?
apply()
是函式原型中的一個重要方法,用於呼叫其他函式, 引數為給定this
和一個陣列(或類似陣列的物件)。
類陣列物件可以參照 NodeList
或函式內的 arguments
物件。
這意味著我們可以呼叫任何函式,並顯式指定呼叫時 this
的指向。
語法
function.apply(this,[argumentsArray])
複製程式碼
返回值
返回 this
指向函式的呼叫結果。
描述
apply()
方法使物件 x
中的函式/物件可以被另一個物件 y
呼叫。
例子
1.
var array = ['a', 'b'];
var elements = [1, 2, 3];
array.push(elements);
console.log(array); // ['a', 'b', [1, 2, 3]]
複製程式碼
以上程式碼中,當我們將一個陣列 push
進另一個陣列時,整個陣列被視為一個元素直接 push
進去。但如果我們想要將陣列 elemennts
中的元素分別 push
進陣列 array
中呢?當然有很多方法可以這樣做,在這裡我們使用 apply()
。
var array = ['a', 'b'];
var elements = [1, 2, 3];
array.push.apply(array, elements);
console.log(array); // ["a", "b", 1, 2, 3]
複製程式碼
該例中,使用 apply
連線給定陣列,引數為陣列 elements
,this
指向變數 array
,實現陣列 elements
中的元素被 push
進 this
指向的物件(array
)中。最終返回的結果為第二個陣列中的每個元素被 push
進 this
指向的陣列中。
2.
var numbers = [53, 65, 25, 37, 78];
console.log(Math.max(numbers)); //NaN
複製程式碼
JS
中 max
函式用於查詢給定元素的最大值。但正如我們所見,如果給定值為陣列,返回結果為 NaN
。當然,JavaScript
中有很多方法可以解決,在這裡我們使用 apply
。
var numbers = [53, 65, 25, 37, 78];
console.log(Math.max.apply(null, numbers)); //78
複製程式碼
當我們使用 apply
呼叫 Math.max()
時,得到了期望結果。apply
將 numbers
中所有值作為單獨的引數,然後再呼叫 max
進行處理,最終返回陣列中的最大值。
值得注意的是,我們使用 null
代替了 this
。由於提供的引數是數字陣列,即使使用了 this
,它也仍會指向同一個陣列,最終得到相同的結果。因此,這種情況下我們可以省略 this
,改用 null
代替。也就是說,apply
函式中的 this
引數是一個可選引數。
Call ?
call()
方法用於呼叫一個函式,引數為給定this
和若干個單獨指定的引數序列。
這意味著我們可以呼叫任何函式,並顯式指定呼叫時 this
的指向。
這與 apply
非常相似,唯一的區別是 apply
以陣列或類陣列物件的形式接受引數,而 call
的引數是單獨提供的。
語法
function.call(thisArg,arg1,arg2,...)
複製程式碼
返回值
返回 this
指向且引數為給定引數的函式呼叫結果。
描述
call()
方法使物件 x
中的一個函式/物件可以被物件 y
呼叫。
例子
1.
function Product(name, price) {
this.name = name;
this.price = price;
}
function Pizza(name, price) {
Product.call(this, name, price);
this.category = 'pizza';
}
function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
}
var pizza = new Pizza('margherita', 50);
var toy = new Toy('robot', 40);
console.log(toy); // Toy {name: "robot", price: 40, category: "toy"}
console.log(pizza); // Pizza {name: "margherita", price: 50, category: "pizza"}
複製程式碼
這是建構函式鏈的一個例子。可以看到,每個函式中都呼叫了 Product
的建構函式,並使用 call
將 Product
物件的屬性分別與 Pizza
和 Toy
連線在一起。
當建立了 Pizza
和 Toy
物件的例項並輸出時,結果顯示其具有 name
,price
和 category
三個屬性,但我們只定義了 category
一個屬性。而屬性name
和 price
則是由於已經在 Product
物件中定義並應用,可通過 Product
物件的鏈式建構函式獲得。將以上程式碼稍作改動即可實現繼承。
2.
function sleep(){
var reply=[this.animal,'typically sleep between',this.sleepDuration].join(' ');
console.log(reply); // I typically sleep between 12 and 16 hours
}
var obj={
animal:'I',sleepDuration:'12 and 16 hours'
};
sleep.call(obj);
複製程式碼
在上面的程式碼中,我們定義了一個名為 sleep
的函式,其包含一個陣列 reply
,該陣列由 this
進行屬性定址得到的元素組成,這些元素在函式外的獨立物件中定義。
呼叫函式 sleep
,其引數為 obj
。可以看到 this.animal
和 this.sleepDuration
分別取到了 obj
的屬性,並輸出了完整的句子。
Bind➰
當
bind()
方法被呼叫時會建立一個新函式,該函式第一個引數作為this
,並在呼叫新函式時提供一個給定的引數序列。 — MDN
譯者注:
bind()
方法會建立一個新函式,稱為繫結函式,當呼叫這個繫結函式時,繫結函式會以建立它時傳入bind()
方法的第一個引數作為this
,第二個以及以後的引數,加上繫結函式執行時本身的引數,按照順序作為原函式的引數來呼叫原函式。
語法
function.bind(this,arg1,arg2,arg3,...)
複製程式碼
返回值
返回 this
指向且引數為給定引數的函式副本的呼叫結果。
描述
bind
方法與 call
方法類似,主要區別在於 bind
返回一個新函式,而 call
不返回。
根據 ECMAScript 5 規範,bind
方法返回的函式是一種特殊型別的函式物件,稱為繫結函式( BF )。BF 中包含原始函式物件,呼叫 BF 時會執行該函式。
例子
var x = 9;
var module = {
x: 81,
getX: function () {
return this.x;
}
};
console.log(module.getX()); // 81
var retrieveX = module.getX;
console.log(retrieveX()); // 9
var boundGetX = retrieveX.bind(module);
console.log(boundGetX()); // 81
複製程式碼
在上面的程式碼中,我們定義了一個變數 x
和一個物件 module
,該物件中還定義了一個屬性 x
以及一個返回 x
值的函式。
當呼叫函式 getX
時,它返回的是物件內定義的 x
的值,而不是全域性作用域中的 x
。
另一個變數在全域性作用域中宣告,並呼叫 module
物件中的 getX
函式。但由於該變數處於全域性作用域下,因此 getX
中的 this
指向全域性作用域下的 x
,返回 9。
最後又定義了另一個變數 boundGetX
,該變數呼叫函式 retrieveX
,與之前不同的是,這次將函式 retrieveX
與物件 module
繫結,返回的是物件內 x
的值。這是由於 bind
將函式中的 this
指向物件中的 x
值而不是全域性 x
,因此輸出 81。
結論
現在我們已經瞭解到以上三種方法的基礎用法,但你可能會疑惑:為什麼要用 3 個不同的方法去做相同的事情。為了解決這個問題,你必須反覆練習在不同場景下它們的使用方法,更全面地瞭解什麼時候使用它們,以及如何更好的使用,這肯定會讓你的程式碼更清晰,更強大。
如果您喜歡這篇文章,點個贊?,加個關注? 吧~