Подтвердить что ты не робот

Почему вызов намного быстрее, чем применяется?

Мне интересно, знает ли кто-нибудь, почему call намного быстрее, чем apply? В chrome он примерно в 4 раза быстрее и 30x в firefox, и я даже могу создать собственный прототип apply2, который (в большинстве случаев) выполняет 2x так же быстро, как apply (идея взята из angular)

Function.prototype.apply2 = function( self, arguments ){
    switch( arguments.length ){
         case 1:  this.call( self, arguments[0] );                                                                                                                               break;
         case 2:  this.call( self, arguments[0], arguments[1] );                                                                                                                 break;
         case 3:  this.call( self, arguments[0], arguments[1], arguments[2] );                                                                                                   break;
         case 4:  this.call( self, arguments[0], arguments[1], arguments[2], arguments[3] );                                                                                     break;
         case 5:  this.call( self, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4] );                                                                       break;
         case 6:  this.call( self, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5] );                                                         break;
         case 7:  this.call( self, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6] );                                           break;
         case 8:  this.call( self, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7] );                             break;
         case 9:  this.call( self, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8] );               break;
         case 10: this.call( self, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9] ); break;
         default: this.apply( self, arguments ); break;
    }   
};

Так кто-нибудь знает, почему?

4b9b3361

Ответ 1

Ссылка на ECMAScript Language Specification 5.1 Edition (июнь 2011 г.):

15.3.4.3 Функция .prototype.apply(thisArg, argArray)

Когда метод apply вызывается в функции func с аргументами thisArg и argArray, выполняются следующие шаги:

  • Если IsCallable(func) - false, то бросьте исключение TypeError.

  • Если argArray - null или undefined, тогда           return результат вызова внутреннего метода [[Call]] func, обеспечивающего thisArg как значение this и пустой список аргументов.

  • Если Type(argArray) не Object, то бросьте исключение TypeError.
  • Пусть len является результатом вызова внутреннего метода [[Get]] argArray с аргументом "length".
  • Пусть n be ToUint32(len).
  • Пусть argList - пустой List.
  • Пусть index будет 0.
  • Повторите, пока index < n
  • Пусть indexName be ToString(index).
  • Пусть nextArg является результатом вызова внутреннего метода [[Get]]  argArray с indexName в качестве аргумента.
  • Добавить nextArg в качестве последнего элемента argList.
  • Установите index в index + 1.
  • Возвращает результат вызова внутреннего метода [[Call]] func,  обеспечивая thisArg как значение this и argList в качестве списка    Аргументы.

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

Когда метод call вызывается для объекта func с аргументом thisArg и необязательными аргументами arg1, arg2 и т.д., предпринимаются следующие шаги:

  • Если IsCallable(func) - false, то бросьте исключение TypeError.
  • Пусть argList - пустой List.
  • Если этот метод вызывается с более чем одним аргументом, то в левом в правильном порядке, начиная с arg1, добавьте каждый аргумент в качестве последнего элемент argList
  • Возвращает результат вызова внутреннего метода [[Call]] func, обеспечивая thisArg как значение this и argList в качестве списка Аргументы.

Как мы видим, формат, в котором указан apply, заметно тяжелее, и ему нужно сделать намного больше из-за необходимости изменить формат, в котором приведены аргументы и как они в конечном итоге необходимы.
Существует ряд проверок в apply, которые не нужны в call из-за разницы форматирования ввода.

Другим ключевым моментом является то, как аргументы зацикливаются (шаги 4-12 в apply, подразумеваемые на шаге 3 из call): вся настройка для цикла выполняется в apply независимо от сколько там аргументов, в call все это делается только при необходимости.
Кроме того, стоит отметить, что способ, которым реализован шаг 3 в call, не указан, что поможет объяснить существенные различия в поведении браузера.

Итак, коротко повторим: call быстрее, чем apply, потому что входные параметры уже отформатированы по мере необходимости для внутреннего метода.

Обязательно прочитайте комментарии ниже для дальнейшего обсуждения.