我一直在查看my.class.js的源代码,以了解Firefox上的fast是什么原因。以下是用于创建类的代码片段:
my.Class = function () {
var len = arguments.length;
var body = arguments[len - 1];
var SuperClass = len > 1 ? arguments[0] : null;
var hasImplementClasses = len > 2;
var Class, SuperClassEmpty;
if (body.constructor === Object) {
Class = function () {};
} else {
Class = body.constructor;
delete body.constructor;
}
if (SuperClass) {
SuperClassEmpty = function() {};
SuperClassEmpty.prototype = SuperClass.prototype;
Class.prototype = new SuperClassEmpty();
Class.prototype.constructor = Class;
Class.Super = SuperClass;
extend(Class, SuperClass, false);
}
if (hasImplementClasses)
for (var i = 1; i < len - 1; i++)
extend(Class.prototype, arguments[i].prototype, false);
extendClass(Class, body);
return Class;
};
extend
函数仅用于将第二个对象的属性复制到第一个(可选择覆盖的现有属性):
var extend = function (obj, extension, override) {
var prop;
if (override === false) {
for (prop in extension)
if (!(prop in obj))
obj[prop] = extension[prop];
} else {
for (prop in extension)
obj[prop] = extension[prop];
if (extension.toString !== Object.prototype.toString)
obj.toString = extension.toString;
}
};
extendClass
函数将所有静态属性复制到类中,并将所有公共属性复制到类的原型中:
var extendClass = my.extendClass = function (Class, extension, override) {
if (extension.STATIC) {
extend(Class, extension.STATIC, override);
delete extension.STATIC;
}
extend(Class.prototype, extension, override);
};
这一切都非常简单。创建类时,它只返回您提供的构造函数。
然而,我理解的是,创建此构造函数execute faster的实例的方法与创建Vapor.js中编写的相同构造函数的实例相比如何。
这就是我想要理解的:
MyFrenchGuy.Super.prototype.setAddress.call
这样的长原型链应该会显着降低它的速度。答案 0 :(得分:11)
我不想冒犯任何人,但这种真的并不值得关注,恕我直言。几乎所有浏览器之间的速度差异都归结为JS引擎。例如,V8引擎非常擅长内存管理;特别是当你将它与IE的旧JScript引擎进行比较时。
请考虑以下事项:
var closure = (function()
{
var closureVar = 'foo',
someVar = 'bar',
returnObject = {publicProp: 'foobar'};
returnObject.getClosureVar = function()
{
return closureVar;
};
return returnObject;
}());
上次检查时,Chrome实际上是GC&#39; someVar
,因为它没有被IIFE的返回值引用(由closure
引用),而两者都是FF和Opera将整个功能范围保存在内存中。
在这个片段中,它并不重要,但对于使用模块模式(AFAIK,几乎所有这些模块)编写的库,它们由数千行代码组成,它就是可以有所作为。
无论如何,现代JS引擎不仅仅是&#34; dumb&#34; 解析和执行的东西。正如你所说:JIT编译正在进行中,但是尽可能地优化代码也需要很多技巧。很可能你发布的片段是以FF的引擎喜欢的方式编写的。
同样重要的是要记住Chrome和FF之间正在进行某种速度战,关于谁拥有最快的引擎。上次我检查Mozilla的Rhino引擎据说胜过谷歌的V8,如果今天仍然如此,我不能说...从那以后,谷歌和Mozilla一直在努力他们的引擎......
底线:存在各种浏览器之间的速度差异 - 没有人可以否认这一点,但是单一的差异点是微不足道的:你永远不会写一个一遍又一遍地做一件事的脚本。重要的是整体表现 你必须记住,JS也是一个棘手的bug来进行基准测试:只需打开你的控制台,编写一些递归函数,然后在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
方法进行比较:没有&em>真正的克隆正在进行,更不用说深度克隆了。它根本没有检查循环引用,这是我所研究过的大多数其他库。检查循环引用确实会减慢整个过程,但它可能会不时派上用场(下面的示例1)。部分性能差异可以解释为这个代码只是做得更少,因此它需要更少的时间。
其次:声明构造函数(JS中不存在类)和创建实例确实是两个不同的事情(尽管声明构造函数本身就是创建一个对象的实例({{1}你编写构造函数的方式可以产生巨大的差异,如下面的例2所示。再次,这是一个泛化,可能不适用于某些用例某些引擎:例如,V8倾向于为所有实例创建单个函数对象,即使该函数是构造函数的一部分 - 或者我告诉过。
第三:如你所说,遍历一个长的原型链并不像你想象的那样不寻常,实际上远非如此。你不断地遍历2或3个原型的链,如例3所示。这不应该让你慢下来,因为它只是JS解析函数调用或解析表达式的固有方式。 / p>
最后:它可能是JIT编译的,但是说其他libs不是JIT编译的,只是没有堆叠。他们可能,然后他们可能不会。正如我之前所说:不同的引擎在某些任务中表现更好,然后其他......它可能可能是FF JIT编译此代码的情况,其他引擎也不是。
我可以看到为什么其他库不能进行JIT编译的主要原因是:检查循环引用,深度克隆功能,依赖性(即Function
方法因各种原因而在各处使用。
示例1:
extend
此函数克隆对象的第一级,仍将共享由原始对象的属性引用的所有对象。一个简单的解决方法是简单地递归调用上面的函数,但是你必须处理所有级别的循环引用的讨厌业务:
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;
};
当然,这不是最常见的情况,但如果你想要防御性地编写代码,你必须承认许多人一直在编写疯狂的代码......
示例2:
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
理论上,声明第一个构造函数较慢而不是凌乱的方式: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
关键字时,第二个构造函数会创建一个全局函数对象,当然也会为每个调用创建新的函数对象。你有一个构造函数(和一个原型),实际上是空转...这将我们带到示例3
示例3:
new
好的,那么幕后发生了什么:var foo = [];//create an array - empty
console.log(foo[123]);//logs undefined.
引用了一个对象,foo
的实例,它继承了Object原型(只是尝试{{1} }})。这是合情合理的,因此Array实例的工作方式与任何对象几乎相同,所以:
Array
换句话说,像你描述的链条并不太牵强或不常见。这就是JS的工作方式,所以期待减慢速度就像期待你的大脑一样,因为你的想法:是的,你可以通过思考太多而疲惫不堪,但只知道什么时候休息一下。就像原型链一样:他们很棒,只知道它们有点慢,是的......
答案 1 :(得分:1)
我不完全确定,但我确实知道在编程时,最好在不牺牲功能的情况下使代码尽可能小。我喜欢称之为minimalist code
。
这可能是混淆代码的一个很好的理由。混淆通过使用较小的方法和变量名来缩小文件的大小,使得更难以进行逆向工程,缩小文件大小,使其下载速度更快,以及潜在的性能提升。 Google的javascript代码非常模糊,这有助于提高速度。
所以在JavaScript中,更大并不总是更好。当我找到一种可以缩小代码的方法时,我会立即实现它,因为我知道它会使性能受益,即使是最小的数量。
例如,在函数外部不需要变量的函数中使用var
关键字有助于垃圾收集,与将变量保存在内存中相比,它提供了非常小的速度提升。
使用这样的库可以产生“每秒数百万次操作”(Blaise的话),小的性能提升可以产生明显/可衡量的差异。
因此my.class.js
可能是“极简主义编码”或以某种方式进行优化。它甚至可以是var
个关键字。
我希望这有所帮助。如果它没有帮助,那么我祝你好运,得到一个好的答案。