Ответ 1
Я не хочу никого обижать, но такого рода вещи не стоит внимания, ИМХО. Почти любая разница в скорости между браузерами сводится к движку JS. Например, двигатель V8 очень хорош в управлении памятью; особенно когда вы сравниваете его с IE JScript двигателями старого.
Рассмотрим следующее:
var closure = (function()
{
var closureVar = 'foo',
someVar = 'bar',
returnObject = {publicProp: 'foobar'};
returnObject.getClosureVar = function()
{
return closureVar;
};
return returnObject;
}());
В прошлый раз, когда я проверил, хром фактически GC'ed someVar
, потому что на него не ссылалось возвращаемое значение IIFE (на которое ссылается closure
), тогда как FF и Opera сохраняли всю функциональную область в Память.
В этом фрагменте это не имеет особого значения, но для libs, которые написаны с использованием модуля-шаблона (AFAIK, что почти все из них), которые состоят из тысяч строк кода, это может иметь значение.
Во всяком случае, современные JS-двигатели - это больше, чем просто "тупые" синтаксические и синтаксические действия. Как вы сказали: там происходит компиляция JIT, но также есть много обмана, чтобы максимально оптимизировать ваш код. Вполне возможно, что фрагмент, который вы опубликовали, написан таким образом, что двигатель FF просто обожает.
Также очень важно помнить, что между Chrome и FF происходит какое-то быстрое сражение, у которого самый быстрый двигатель. В прошлый раз, когда я проверял, что движок Mozilla Rhino, как говорили, превосходит Google V8, если это все еще сохраняется сегодня, я не могу сказать... С тех пор как Google, так и Mozilla работают над своими двигателями...
Внизу: существуют различия в скорости между различными браузерами - никто не может этого отрицать, но одна точка различия незначительна: вы никогда не напишете script, который делает одно и то же снова и снова. Это важная общая производительность.
Вы должны иметь в виду, что JS тоже сложный баггер для тестирования: просто откройте консоль, запишите некоторую рекурсивную функцию и выполните ее 100 раз, в FF и Chrome. сравнить время, необходимое для каждой рекурсии, и общий пробег. Затем подождите пару часов и повторите попытку... иногда FF может появиться сверху, тогда как в других случаях Chrome может быть быстрее. Я пробовал это с помощью этой функции:
var bench = (function()
{
var mark = {start: [new Date()],
end: [undefined]},
i = 0,
rec = function(n)
{
return +(n === 1) || rec(n%2 ? n*3+1 : n/2);
//^^ Unmaintainable, but fun code ^^\\
};
while(i++ < 100)
{//new date at start, call recursive function, new date at end of recursion
mark.start[i] = new Date();
rec(1000);
mark.end[i] = new Date();
}
mark.end[0] = new Date();//after 100 rec calls, first element of start array vs first of end array
return mark;
}());
Но теперь, чтобы вернуться к вашему первоначальному вопросу:
Во-первых: предоставленный вами фрагмент не совсем сравним, скажем, с методом jQuery $.extend
: не происходит реального клонирования, не говоря уже о глубоком клонировании. Он не проверяет циклические ссылки вообще, что большинство других библиотек, которые я изучил, делает. проверка циклических ссылок замедляет весь процесс, но время от времени оно может пригодиться (пример 1 ниже). Часть разницы в производительности может быть объяснена тем, что этот код просто делает меньше, поэтому ему требуется меньше времени.
Во-вторых: объявление конструктора (классы не существуют в JS), а создание экземпляра - это, разумеется, две разные вещи (хотя объявление конструктора само по себе создает экземпляр объекта (экземпляр Function
точно так же, как вы пишете свой конструктор, может иметь огромное значение, как показано ниже в примере 2. Опять же, это обобщение и может не относиться к определенным вариантам использования для определенных движков: например, V8 создает единый функциональный объект для всех экземпляров, даже если эта функция является частью конструктора - или так мне говорят.
В-третьих: Прохождение длинной прототип-цепи, как вы говорите, не так необычно, как вы могли бы думать, далеки от нее. Вы постоянно просматриваете цепочки из 2 или трех прототипов, как показано в примере 3. Это не должно замедлять вас, поскольку это просто связано с тем, как JS разрешает вызовы функций или разрешает выражения.
Наконец: он, вероятно, скомпилирован JIT, но говорит, что другие libs не скомпилированы JIT, просто не складываются. Тогда они могут, возможно, и нет. Как я уже говорил, разные двигатели лучше выполняют некоторые задачи, чем другие... возможно, это так, что FF JIT-компилирует этот код, а другие - нет.
Основная причина, по которой я могу понять, почему другие библиотеки не будут скомпилированы JIT, - это проверка циклических ссылок, возможности глубокого клонирования, зависимости (т.е. Метод extend
используется повсюду по разным причинам).
пример 1:
var shallowCloneCircular = function(obj)
{//clone object, check for circular references
function F(){};
var clone, prop;
F.prototype = obj;
clone = new F();
for (prop in obj)
{//only copy properties, inherent to instance, rely on prototype-chain for all others
if (obj.hasOwnProperty(prop))
{//the ternary deals with circular references
clone[prop] = obj[prop] === obj ? clone : obj[prop];//if property is reference to self, make clone reference clone, not the original object!
}
}
return clone;
};
Эта функция клонирует объект первого уровня, все объекты, на которые ссылается свойство исходного объекта, по-прежнему будут совместно использоваться. Простым решением было бы просто вызывать функцию выше рекурсивно, но тогда вам придется иметь дело с неприятным бизнесом круговых ссылок на всех уровнях:
var circulars = {foo: bar};
circulars.circ1 = circulars;//simple circular reference, we can deal with this
circulars.mess = {gotcha: circulars};//circulars.mess.gotcha ==> circular reference, too
circulars.messier = {messiest: circulars.mess};//oh dear, this is hell
Конечно, это не самая распространенная ситуация, но если вы хотите защитить свой код, вы должны признать, что многие люди все время пишут сумасшедший код...
Пример 2:
function CleanConstructor()
{};
CleanConstructor.prototype.method1 = function()
{
//do stuff...
};
var foo = new CleanConstructor(),
bar = new CleanConstructor);
console.log(foo === bar);//false, we have two separate instances
console.log(foo.method1 === bar.method1);//true: the function-object, referenced by method1 has only been created once.
//as opposed to:
function MessyConstructor()
{
this.method1 = function()
{//do stuff
};
}
var foo = new MessyConstructor(),
bar = new MessyConstructor();
console.log(foo === bar);//false, as before
console.log(foo.method1 === bar.method1);//false! for each instance, a new function object is constructed, too: bad performance!
Теоретически объявление первого конструктора медленнее, чем беспорядочный: объект-объект, на который ссылается method1
, создается до создания одного экземпляра. Во втором примере не создается method1
, кроме тех случаев, когда вызывается конструктор. Но недостатки огромны: забудьте ключевое слово new
в первом примере, и все, что вы получите, - это возвращаемое значение undefined
. Второй конструктор создает глобальный объект функции, когда вы опускаете ключевое слово new
и, конечно, создает новые функциональные объекты для каждого вызова. У вас есть конструктор (и прототип), который, фактически, работает на холостом ходу... Что приводит нас к примеру 3
Пример 3:
var foo = [];//create an array - empty
console.log(foo[123]);//logs undefined.
Итак, что происходит за кулисами: foo
ссылается на объект, экземпляр Array
, который, в свою очередь, наследует форму прототипа Object (просто попробуйте Object.getPrototypeOf(Array.prototype)
). Разумеется, поэтому экземпляр Array работает почти так же, как любой объект, поэтому:
foo[123] ===> JS checks instance for property 123 (which is coerced to string BTW)
|| --> property not found @instance, check prototype (Array.prototype)
===========> Array.prototype.123 could not be found, check prototype
||
==========> Object.prototype.123: not found check prototype?
||
=======>prototype is null, return undefined
Другими словами, цепочка, как вы описываете, не слишком надуманна или необычна. Это, как работает JS, поэтому ожидая, что замедлить работу, это похоже на то, что ваш мозг жарится, потому что вы думаете: да, вы можете измучить, слишком много думая, но просто знаете, когда нужно сделать перерыв. Как и в случае прототипов-цепочек: их отличные, просто знайте, что они немного медленнее, да...