编程语言
首页 > 编程语言> > javascript – 是什么让my.class.js如此之快?

javascript – 是什么让my.class.js如此之快?

作者:互联网

我一直在查看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);
};

这一切都非常简单.创建类时,它只返回您提供的构造函数.

然而,我理解的是,如何创建此构造函数execute faster的实例,而不是创建在Vapor.js中编写的相同构造函数的实例.

这就是我想要了解的内容:

>像my.class.js这样的库的构造函数如何在Firefox上如此快速地创建如此多的实例?库的构造函数都非常相似.执行时间不应该相似吗?
>为什么创建类的方式会影响实例化的执行速度?定义和实例化不是单独的进程吗?
> my.class.js从哪里获得这种速度提升?我没有看到构造函数代码的任何部分应该使它更快地执行.实际上遍历像MyFrenchGuy.Super.prototype.setAddress.call这样的长原型链应该会显着降低它的速度.
>构造函数是否正在编译JIT?如果是这样,为什么其他库的构造函数也不是JIT编译的?

解决方法:

我并不是要冒犯任何人,但这种事情真的不值得关注,恕我直言.几乎所有浏览器之间的速度差异都归结为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的返回值引用(由闭包引用),而FF和Opera都将整个函数范围保留在内存中.
在这个片段中,它并不重要,但对于使用由数千行代码组成的模块模式(AFAIK,几乎所有这些)编写的库,它可以有所作为.

无论如何,现代JS引擎不仅仅是“愚蠢”的解析和执行事物.正如你所说: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方法并不完全相比:没有真正的克隆,更不用说深度克隆了.它根本不检查循环引用,这是我调查过的大多数其他库.检查循环引用确实会减慢整个过程,但它可能会不时派上用场(下面的示例1).部分性能差异可以解释为这个代码只是做得更少,因此需要的时间更少.

其次:声明构造函数(JS中不存在类)和创建实例确实是两个不同的事情(尽管声明构造函数本身就是创建一个对象的实例(确切地说是一个Function实例).你编写的构造函数可以产生很大的不同,如下面的例2所示.再次,这是一个泛化,可能不适用于某些引擎上的某些用例:例如,V8往往会创建一个单独的函数对象所有实例,即使该函数是构造函数的一部分 – 或者我被告知.

第三:如你所说,遍历一个长的原型链并不像你想象的那样不寻常,实际上远非如此.你经常遍历2或3个原型的链,如例3所示.这不应该让你失望,因为它只是JS解析函数调用或解析表达式的固有方式.

最后:它可能是JIT编译的,但是说其他的libs不是JIT编译的,只是不会叠加.他们可能,然后他们可能不会.正如我之前所说:不同的引擎在某些任务中表现更好,然后是其他……可能是FF JIT编译此代码的情况,而其他引擎则不然.
我可以看到为什么其他库不会被JIT编译的主要原因是:检查循环引用,深度克隆功能,依赖性(即由于各种原因,遍布各处使用扩展方法).

例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的工作方式,所以期待减慢速度就像期待你的大脑一样,因为你的想法:是的,你可以通过思考太多而疲惫不堪,但只知道什么时候休息一下.就像原型链一样:他们很棒,只知道它们有点慢,是的……

标签:javascript,oop,instantiation,jsperf
来源: https://codeday.me/bug/20190926/1820701.html