這是一個非常有意思的問題。 在看源碼的過程中,總會遇到這樣的寫法:
var triggerEvents = function(events, args) {
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
switch (args.length) {
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
}
};
( 代碼來自 backbone )
作者會在參數爲3個(包含3)以內時,優先使用 call 方法進行事件的處理。而當參數過多(多餘3個)時,才考慮使用 apply 方法。 這個的原因就是 call 比 apply 快。
網上有很多例子全方位的證明了 call 比 apply 快。大家可以看看
call和apply的性能對比 這篇文章中的例子,很全面。或者你也可以自己寫幾個簡單的,測試一下。這裏要推薦一個神奇網站 jsperf,用於測試
js 性能。
幾個簡單的例子:
爲什麼call 比apply 快? 這裏就要提到他們被調用之後發生了什麼。
Function.prototype.apply (thisArg, argArray)
- 如果IsCallable(Function)爲false,即Function不可以被調用,則拋出一個TypeError異常。
- 如果argArray爲null或未定義,則返回調用function的[[Call]]內部方法的結果,提供thisArg和一個空數組作爲參數。
- 如果 Type(argArray)不是Object,則拋出TypeError異常。
- 獲取argArray的長度。調用argArray的[[Get]]內部方法,找到屬性length。 賦值給len。
- 定義 n 爲ToUint32(len)。ToUint32(len)方法:將其參數len轉換爲範圍爲0到2^32-1的2^32個整數值中的一個。
- 初始化 argList 爲一個空列表。
- 初始化 index 爲 0。
-
循環迭代取出argArray。重複循環 while(index 1. 將下標轉換成String類型。初始化 indexName 爲 ToString(index).
- 定義 nextArg 爲 使用 indexName 作爲參數調用argArray的[[Get]]內部方法的結果。
- 將 nextArg 添加到 argList 中,作爲最後一個元素。
- 設置 index = index+1 。
- 返回調用func的[[Call]]內部方法的結果,提供thisArg作爲該值,argList作爲參數列表。
Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )
- 如果 IsCallable(Function)爲false,即Function不可以被調用,則拋出一個TypeError異常。
- 定義argList 爲一個空列表。
- 如果使用超過一個參數調用此方法,則以從arg1開始的從左到右的順序將每個參數附加爲argList的最後一個元素
- 返回調用func的[[Call]]內部方法的結果,提供thisArg作爲該值,argList作爲參數列表。
我們可以看到,明顯apply比call的步驟多很多。 由於apply中定義的參數格式(數組),使得被調用之後需要做更多的事,需要將給定的參數格式改變(步驟8)。 同時也有一些對參數的檢查(步驟2),在call中卻是不必要的。 另外一個很重要的點:在apply中不管有多少個參數,都會執行循環,也就是步驟6-8,在call中也就是對應步驟3 ,是有需要纔會被執行。
綜上,call 方法比 apply 快的原因是 call 方法的參數格式正是內部方法所需要的格式。