是什么让my.class.js这么快? [关闭](What makes my.class.js

2019-07-17 23:23发布

我一直在寻找的源代码my.class.js找出是什么使得它如此快的Firefox。 这里有一个代码用于创建一个类的代码片段:

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);
};

这一切非常简单。 当你创建一个类,它只是返回你提供给它的构造函数。

然而什么击败我的理解是怎么做创建这个构造的一个实例执行得更快比创建写在同一个构造函数的一个实例Vapor.js 。

这就是我想了解:

  1. 怎么办图书馆的建设者像my.class.js在Firefox如此快速地创建这么多的情况下? 图书馆的所有构造函数是非常相似的。 如果不执行时间也相似?
  2. 为什么类的创建方式影响实例的执行速度? 难道定义和实例化单独的进程?
  3. 凡my.class.js获得此速度提升? 我没有看到构造函数代码应该让它执行任何更快的任何部分。 其实遍历一个长的原型链类似MyFrenchGuy.Super.prototype.setAddress.call应该显著慢下来。
  4. 被JIT编译的构造函数? 如果是的话,为什么没有其他库的构造函数也正在编制JIT?

Answer 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保存在内存中的整个功能范围。
在这个片段中,它其实并不重要,但对于那些使用模块的模式书面库(据我所知,这几乎是所有的人)是由数千行代码,就可以有所作为。

总之,现代JS-引擎不仅仅是“哑”语法分析和执行的东西更多。 至于你说:有JIT编译事,但也有参与,以尽可能多地优化你的代码有很多挂羊头卖狗肉的。 它很可能是您发布的片断被写在FF的发动机非常喜欢的一种方式。
这也是很重要的是要记住,有某种的速度战谁拥有最快的引擎Chrome和FF之间的事情。 我最后一次检查Mozilla的Rhino引擎,据说优于谷歌的V8,如果今天仍然成立,我不能说......从此,谷歌和Mozilla都一直在他们的引擎...

底线:不同浏览器之间的速度存在差异 - 没有人可以否认,但差的单点微不足道的:你永远不会写一个脚本,只有一件事一遍又一遍做。 它是重要的整体性能。
你必须记住,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,例如,趋向于建立一个单一的功能对象任何情况下,即使该功能是构造的一部分 - 所以有人告诉我。

第三:遍历一个长原型链,你提到是不是不寻常,因为你可能会认为,远离它,其实。 你不断穿越的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.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如何工作的,所以期待,要慢下来就像是期待你的大脑炒,因为你的思维:是的,你可以得到由想得太多了累死了,只是知道什么时候休息一下。 就像在原型链的情况:他们的伟大,只知道他们是一点点慢,是...



Answer 2:

我不能完全肯定,但我确实知道,在编程时,这是很好的做法,使代码尽可能小不牺牲功能。 我喜欢把它称为minimalist code

这可能是一个很好的理由来混淆代码。 混淆通过使用更小的方法和变量名缩小了文件的大小,使其更难进行反向工程,缩小文件大小,使其更快的下载,以及潜在的性能提升。 谷歌的JavaScript代码混淆强烈,这有助于他们的速度。

因此,在JavaScript中,更大并不总是更好。 当我找到一个方式,我可以缩小我的代码,我即使以最小量立即实现它,因为我知道这将有利于性能。

例如,使用该var在不需要函数外部变量的函数关键字有助于垃圾收集,从而提供了非常小的速度提升与保持变量在内存中。

像这样这样产生“每秒百万次运算的”(Blaise的话),图书馆,小的性能提升最多可以添加到一个显着的/可测量的差异。

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

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



文章来源: What makes my.class.js so fast? [closed]