我并不是要冒犯任何人,但是这种事情真的不值得关注,恕我直言。几乎所有浏览器之间的速度差异都取决于 JS 引擎。例如,V8 引擎非常擅长内存管理;特别是当您将它与旧的 IE 的 JScript 引擎进行比较时。
考虑以下:
var closure = (function()
{
var closureVar = 'foo',
someVar = 'bar',
returnObject = {publicProp: 'foobar'};
returnObject.getClosureVar = function()
{
return closureVar;
};
return returnObject;
}());
上次我检查时,chrome 实际上是 GC'ed someVar
,因为它没有被 IIFE 的返回值引用(由 引用closure
),而 FF 和 Opera 都将整个函数范围保存在内存中。
在这个片段中,这并不重要,但是对于使用包含数千行代码的模块模式(AFAIK,几乎是所有这些)编写的库,它可以有所作为。
无论如何,现代 JS 引擎不仅仅是“愚蠢的”解析和执行的东西。正如您所说:正在进行 JIT 编译,但也有很多技巧可以尽可能地优化您的代码。您发布的片段很可能是以 FF 引擎喜欢的方式编写的。
同样重要的是要记住,Chrome 和 FF 之间正在就谁拥有最快的引擎进行某种速度之战。上次我检查 Mozilla 的 Rhino 引擎时,据说性能优于 Google 的 V8,如果今天仍然如此,我不能说……从那时起,Google 和 Mozilla 都在研究他们的引擎……
底线:不同浏览器之间存在速度差异 - 没有人可以否认这一点,但单点差异是微不足道的:你永远不会编写一个一遍又一遍地只做一件事的脚本。重要的是整体性能。
你必须记住,JS 也是一个棘手的基准测试程序:只需打开你的控制台,编写一些递归函数,然后在 FF 和 Chrome 中运行 100 次。比较每次递归所需的时间和整体运行时间。然后等待几个小时再试一次……有时 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 个原型链,如示例 3 所示。这不应该减慢您的速度,因为它只是 JS 解析函数调用或解析表达式的方式所固有的。
最后:它可能是 JIT 编译的,但是说其他库不是 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
:在创建单个实例之前创建引用的函数对象。第二个示例不创建 a method1
,除非在调用构造函数时。但是缺点是巨大的:忘记new
第一个例子中的关键字,你得到的只是一个返回值undefined
。第二个构造函数在省略new
关键字时创建一个全局函数对象,当然每次调用都会创建新的函数对象。你有一个构造函数(和一个原型),事实上,它是空闲的......这将我们带到示例 3
示例 3:
var foo = [];//create an array - empty
console.log(foo[123]);//logs undefined.
好的,那么幕后会发生什么:foo
引用一个object, instance of Array
,而后者又继承了 Object 原型(只是 try 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 的工作原理,所以期待它放慢速度就像期待你的大脑因为你的想法而煎炸:是的,你可能会因为想太多而筋疲力尽,但要知道什么时候该休息一下。就像原型链的情况一样:它们很棒,只知道它们有点慢,是的......