2013-01-08 87 views
-3

我一直在寻找my.class.js的源代码,以便找出在Firefox上使用fast的原因。下面是代码用于创建一类片段:是什么让my.class.js这么快?

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中编写的相同构造函数的实例。

这是什么,我想了解:

  1. 如何库的构造就像my.class.js在Firefox如此快速地创建这么多的情况下?这些库的构造函数都非常相似。执行时间是否也应该相似?
  2. 为什么类的创建方式会影响实例化的执行速度?不是定义和实例化的独立过程吗?
  3. my.class.js从哪里获得这个速度提升?我没有看到任何应该使它更快执行的构造函数代码。事实上,穿越像MyFrenchGuy.Super.prototype.setAddress.call这样长的原型链应该会显着减慢它的速度。
  4. 构造函数是否被JIT编译?如果是这样的话,为什么其他库的构造函数不是被JIT编译的呢?
+2

导致my.class.js更快的一个方面是扩展函数不执行'hasOwnProperty'检查。 ---但是当你考虑到它每秒执行数百万次操作的事实时,jsperf测试中快速执行的库之间的差异是最小的。如果您创建了许多对象实例,那么您将会遇到与内存占用和垃圾收集相关的其他问题。 – Blaise

回答

11

我并不是要冒犯任何人,但这种事真的是不值得关注,恕我直言。几乎所有浏览器之间的速度差异都取决于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将整个功能范围保留在内存中。
在这个片段中,它并不重要,但是对于使用模块模式(AFAIK,这几乎全部是由几千行代码组成)编写的库,可以使有所作为。

总之,现代JS-引擎不仅仅是“哑巴”语法分析和执行的东西更多。正如你所说:JIT编译正在进行中,但也有很多欺骗手段要尽可能优化你的代码。很可能你发布的代码片段的写法是FF引擎喜欢
这也是很重要的是要记住,有某种的速度战谁拥有最快的引擎Chrome和FF之间的事情。我上次查看Mozilla的Rhino引擎据说超越了Google的V8,如果今天仍然如此,我不能说...自那时以来,Google和Mozilla一直在努力开发他们的引擎...

Bottom线:存在各种浏览器之间的速度差异 - 没有人能否认这一点,但单点的差异是微不足道的:你永远不会写一个脚本,一次又一次地做一件事。重要的是整体表现。
你必须记住,JS是一个棘手的开溜的基准,也:只需打开控制台,写一些递归函数,并敲响它的100倍,在FF和铬。比较每次递归所需的时间和整体运行。然后等待几个小时,然后再试一次......有时候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; 
}()); 

但现在,要回你最初的问题(S):

第一关:你提供的并不完全比较,说的片段, jQuery的$.extend方法:没有真正克隆回事,更不用说深克隆。它根本不检查循环引用,我查看过的大多数其他库都是这样做的。检查循环引用确实会减慢整个过程,但它可能会不时派上用场(下面的示例1)。部分性能差异可以通过以下事实来解释:此代码只是简单地少了,因此它需要更少的时间。其次:声明一个构造函数(类中不存在JS)和创建一个实例实际上是两个不同的事情(尽管声明一个构造函数本身就是创建一个对象的实例(一个Function实例是准确的)如下面的例子2所示,你编写构造函数的方式可以产生一个巨大的差异,同样,这是一个泛化,并且可能不适用于某些引擎的某些使用情况:例如,V8倾向于创建所有实例单一功能的对象,即使该功能是构造的一部分 - 所以我告诉

三:遍历一个长原型链,你提到并不像你想象的那么不寻常,远离我实际上。如例3所示,你不断地遍历2或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引用。第二个示例不创建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如何工作的原因,所以期望减慢速度就像期待着你的大脑因为你的想法而f然心动:是的,你可以通过思考太多而疲惫不堪,但只是知道什么时候休息一下。就像在原型链的情况下一样:它们很棒,只知道它们慢一点,是的...

+0

请不要介意表决,只要他们有动力。 –

1

虽然我并不完全确定,但我确实知道在编程时,尽可能缩小代码而不牺牲功能是个好习惯。我喜欢叫它minimalist code

这可能是混淆代码的一个很好的理由。混淆通过使用更小的方法和变量名称来缩小文件的大小,使其难以进行反向工程,缩小文件大小,使其下载速度更快,并且可能会提高性能。谷歌的JavaScript代码被强烈混淆,这对他们的速度有所贡献。

所以在JavaScript中,大一点并不总是更好。当我找到可以缩小代码的方法时,我立即执行它,因为我知道即使数量最小,它也会使性能受益。

例如,在函数外部不需要变量的函数中使用var关键字有助于垃圾回收,它提供了非常小的速度提升而不是将内存中的变量保留。如果这样的库产生“每秒数百万次操作”(Blaise的话),小的性能提升可以增加明显的/可测量的差异。

所以有可能的是my.class.js是“极简编码”或以某种方式进行了优化。它甚至可能是var关键字。

我希望这有助于一些。如果它没有帮助,那么我祝你好运,得到一个很好的答案。

+0

这就是我的想法,但小也不总是更快。在JavaScript中查看我的[augment](http://javascript.github.com/augment/#section-20“augment.js”)方法(它只有7行代码)。它确实是'my.class.js'和其他库所做的,但它仍然[数量级慢](http://jsperf.com/oop-benchmark/108“JavaScript面向对象的库基准测试版?jsPerf”)他们在Firefox上(但是它是Opera上速度最快的一款,在Chrome 23上是第二快)。我真正想知道的是为什么其他库在Firefox上创建实例的速度如此之快。 –

+0

这很有趣。然而,Firefox的运行环境与Opera不同(Opera的HTML5/CSS和Caraken的JavaScript)和Chrome(Webkit的HTML/CSS和JavaScript的V8)不同。也许它是引擎。 –