為什麼 call 比 apply 快?

李佳怡發表於2017-09-19

這是一個非常有意思的問題。

在看原始碼的過程中,總會遇到這樣的寫法:

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 快?

為什麼 call 比 apply 快?

為什麼call 比 apply 快?
這裡就要提到他們被呼叫之後發生了什麼。

Function.prototype.apply (thisArg, argArray)

1、如果 IsCallable(Function)為false,即 Function 不可以被呼叫,則丟擲一個 TypeError 異常。
2、如果 argArray 為 null 或未定義,則返回撥用 Function 的 [[Call]] 內部方法的結果,提供thisArg 和一個空陣列作為引數。
3、如果 Type(argArray)不是 Object,則丟擲 TypeError 異常。
4、獲取 argArray 的長度。呼叫 argArray 的 [[Get]] 內部方法,找到屬性 length。 賦值給 len。
5、定義 n 為 ToUint32(len)。
6、初始化 argList 為一個空列表。
7、初始化 index 為 0。
8、迴圈迭代取出 argArray。重複迴圈 while(index < n)
         a、將下標轉換成String型別。初始化 indexName 為 ToString(index).
         b、定義 nextArg 為 使用 indexName 作為引數呼叫argArray的[[Get]]內部方法的結果。
         c、將 nextArg 新增到 argList 中,作為最後一個元素。
         d、設定 index = index+1
9、返回撥用 Function 的 [[Call]] 內部方法的結果,提供 thisArg 作為該值,argList 作為引數列表。

Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )

1、如果 IsCallable(Function)為 false,即 Function 不可以被呼叫,則丟擲一個 TypeError 異常。
2、定義 argList 為一個空列表。
3、如果使用超過一個引數呼叫此方法,則以從arg1開始的從左到右的順序將每個引數附加為 argList 的最後一個元素
4、返回撥用func的[[Call]]內部方法的結果,提供 thisArg 作為該值,argList 作為引數列表。


我們可以看到,明顯 apply 比 call 的步驟多很多。
由於 apply 中定義的引數格式(陣列),使得被呼叫之後需要做更多的事,需要將給定的引數格式改變(步驟8)。 同時也有一些對引數的檢查(步驟2),在 call 中卻是不必要的。
另外一個很重要的點:在 apply 中不管有多少個引數,都會執行迴圈,也就是步驟 6-8,在 call 中也就是對應步驟3 ,是有需要才會被執行。

綜上,call 方法比 apply 快的原因是 call 方法的引數格式正是內部方法所需要的格式。


catch me:

知乎:李佳怡



相關文章