是什么让my.class.js如此之快? [英] What makes my.class.js so fast?

查看:150
本文介绍了是什么让my.class.js如此之快?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在查看我的资料来源代码。 class.js 找出是什么让它如此在Firefox上快速。以下是用于创建类的代码片段:

I've been looking at the source code of my.class.js to find out what makes it so fast on Firefox. Here's the snippet of code used to create a class:

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 函数仅用于复制第一个对象的属性(可选择覆盖现有属性):

The extend function is simply used to copy the properties of the second object onto the first (optionally overriding existing properties):

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 函数复制所有静态属性在类上,以及所有公共属性到类的原型:

The extendClass function copies all the static properties onto the class, as well as all the public properties onto the prototype of the class:

var extendClass = my.extendClass = function (Class, extension, override) {
    if (extension.STATIC) {
        extend(Class, extension.STATIC, override);
        delete extension.STATIC;
    }
    extend(Class.prototype, extension, override);
};

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

This is all pretty straightforward. When you create a class, it simply returns the constructor function you provide it.

然而,我理解的是如何创建这个构造函数的实例执行速度快于创建 Vapor.js

What beats my understanding however is how does creating an instance of this constructor execute faster than creating an instance of the same constructor written in Vapor.js.

这就是我想要理解的:


  1. 像my.class.js这样的库的构造函数如何创建Firefox上这么多实例如此之快?库的构造函数都非常相似。执行时间不应该相似吗?

  2. 为什么创建类的方式会影响实例化的执行速度?定义和实例化不是单独的流程吗?

  3. my.class.js从哪里获得这个速度提升?我没有看到构造函数代码的任何部分应该使它更快地执行。事实上,遍历一个长的原型链,如 MyFrenchGuy.Super.prototype.setAddress.call ,应该会大大减慢它。

  4. 是构造函数函数被JIT编译?如果是这样,为什么其他库的构造函数也不是JIT编译的?

  1. How do constructors of libraries like my.class.js create so many instances so quickly on Firefox? The constructors of the libraries are all very similar. Shouldn't the execution time also be similar?
  2. Why does the way the class is created affect the execution speed of instantiation? Aren't definition and instantiation separate processes?
  3. Where is my.class.js gaining this speed boost from? I don't see any part of the constructor code which should make it execute any faster. In fact traversing a long prototype chain like MyFrenchGuy.Super.prototype.setAddress.call should slow it down significantly.
  4. Is the constructor function being JIT compiled? If so then why aren't the constructor functions of other libraries also being JIT compiled?


推荐答案

我并不是要冒犯任何人,但这种事情真的不值得关注,恕我直言。几乎所有浏览器之间的速度差异都归结为JS引擎。例如,V8引擎非常擅长内存管理;特别是当你将它与IE的旧JScript引擎进行比较时。

I don't mean to offend anyone, but this sort of thing really isn't worth the attention, IMHO. Almost any speed-difference between browsers is down to the JS engine. The V8 engine is very good at memory management, for example; especially when you compare it to IE's JScript engines of old.

考虑以下因素:

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,几乎所有这些)编写的库,它可以有所作为。

Last time I checked, chrome actually GC'ed someVar, because it wasn't being referenced by the return value of the IIFE (referenced by closure), whereas both FF and Opera kept the entire function scope in memory.
In this snippet, it doesn't really matter, but for libs that are written using the module-pattern (AFAIK, that's pretty much all of them) that consist of thousands of lines of code, it can make a difference.

无论如何,现代JS引擎不仅仅是dumb解析和执行的东西。正如你所说:JIT编译正在进行中,但也有很多技巧可以尽可能地优化你的代码。很可能你发布的片段是以FF的引擎喜欢的方式编写的。

同样重要的是要记住,Chrome和FF之间存在某种速度战,关于谁拥有最快的引擎。上次我检查Mozilla的Rhino引擎据说胜过谷歌的V8,如果今天仍然如此,我不能说......从那以后,谷歌和Mozilla一直在研发他们的引擎...

Anyway, modern JS-engines are more than just "dumb" parse-and-execute things. As you said: there's JIT compilation going on, but there's also a lot of trickery involved to optimize your code as much as possible. It could very well be that the snippet you posted is written in a way that FF's engine just loves.
It's also quite important to remember that there is some sort of speed-battle going on between Chrome and FF about who has the fastest engine. Last time I checked Mozilla's Rhino engine was said to outperform Google's V8, if that still holds true today, I can't say... Since then, both Google and Mozilla have been working on their engines...

底线:存在各种浏览器之间的速度差异 - 没有人可以否认这一点,但单点差异是微不足道的:你永远不会写一个只反复做一件事的脚本。这是整体性能的重要性。

你必须记住,JS也是一个棘手的bug来进行基准测试:只需打开你的控制台,编写一些递归函数,并在FF中编写100次,在FF中和Chrome。比较每次递归所需的时间和整个运行时间。然后等待几个小时再试一次......有时候FF可能会出现在顶部,而有时候Chrome可能会更快。我尝试过这个函数:

Bottom line: speed differences between various browsers exist - nobody can deny that, but a single point of difference is insignificant: you'll never write a script that does just one thing over and over again. It's the overall performance that matters.
You have to keep in mind that JS is a tricky bugger to benchmark, too: just open your console, write some recursive function, and rung it 100 times, in FF and Chrome. compare the time it takes for each recursion, and the overall run. Then wait a couple of hours and try again... sometimes FF might come out on top, whereas other times Chrome might be faster, still. I've tried it with this function:

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

但是现在,回到最初的问题:

But now, to get back to your initial question(s):

首先关闭:你提供的代码片段与jQuery的 $ .extend 方法并不完全相比:没有 real 克隆正在进行,更不用说深度克隆了。它根本不检查循环引用,这是我调查过的大多数其他库。检查循环引用确实会减慢整个过程,但它可能会不时派上用场(下面的示例1)。部分性能差异可以通过以下事实解释:此代码只需更少,因此需要更少的时间。

First off: the snippet you provided doesn't quite compare to, say, jQuery's $.extend method: there's no real cloning going on, let alone deep-cloning. It doesn't check for circular references at all, which most other libs I've looked into do. checking for circular references does slow the entire process down, but it can come in handy from time to time (example 1 below). Part of the performance difference could be explained by the fact that this code simply does less, so it needs less time.

其次:声明一个构造函数(JS中不存在类)并且创建实例确实是两个不同的东西(尽管声明构造函数本身就是创建对象的实例( Function 准确的实例)。编写构造函数的方式可以产生巨大的差异,如下面的示例2所示。再次,这是一个泛化,可能不适用于某些用例某些引擎:例如,V8倾向于为所有实例创建单个函数对象,即使该函数是构造函数的一部分 - 或者我被告知。

Secondly: Declaring a constructor (classes don't exist in JS) and creating an instance are, indeed, two different things (though declaring a constructor is in itself creating an instance of an object (a Function instance to be exact). The way you write your constructor can make a huge difference, as shown in example 2 below. Again, this is a generalization, and might not apply to certain use-cases on certain engines: V8, for example, tends to create a single function object for all instances, even if that function is part of the constructor - or so I'm told.

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

Thirdly: Traversing a long prototype-chain, as you mention is not as unusual as you might think, far from it, actually. You're constantly traversing chains of 2 or three prototypes, as shown in example 3. This shouldn't slow you down, as it's just inherent to the way JS resolves function calls or resolves expressions.

最后:它可能是JIT编译的,但是说其他的libs不是JIT编译的没有堆积。他们可能,然后他们可能不会。正如我之前所说的:不同的引擎在某些任务中表现更好,然后其他......它可能可能是FF JIT编译此代码的情况,而其他引擎则不然。

我可以看到为什么其他库不会被JIT编译的主要原因是:检查循环引用,深度克隆功能,依赖性(即 extend 方法在各地使用,原因各种各样。)

Lastly: It's probably being JIT-compiled, but saying that other libs aren't JIT-compiled just doesn't stack up. They might, then again, they might not. As I said before: different engines perform better at some tasks then other... it might be the case that FF JIT-compiles this code, and other engines don't.
The main reason I can see why other libs wouldn't be JIT-compiled are: checking for circular references, deep cloning capabilities, dependencies (ie extend method is used all over the place, for various reasons).

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

此函数克隆对象的第一级,所有被原始对象的属性引用的对象,仍然会被分享。一个简单的解决方法是简单地递归调用上面的函数,但是你将不得不处理所有级别的循环引用的讨厌业务:

This function clones an object's first level, all objects that are being referenced by a property of the original object, will still be shared. A simple fix would be to simply call the function above recursively, but then you'll have to deal with the nasty business of circular references at all levels:

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

当然,这不是最常见的情况,但是如果你想要防御性地编写代码,你必须承认许多人一直在编写疯狂的代码......

Of course, this isn't the most common of situations, but if you want to write your code defensively, you have to acknowledge the fact that many people write mad code all the time...

示例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 关键字,所得到的只是的返回值未定义。当省略 new 关键字时,第二个构造函数会创建一个全局函数对象,当然也会为每个调用创建新的函数对象。你有一个构造函数(和一个原型),实际上是空转...这将我们带到示例3

In theory, declaring the first constructor is slower than the messy way: the function object, referenced by method1 is created before a single instance has been created. The second example doesn't create a method1, except for when the constructor is called. But the downsides are huge: forget the new keyword in the first example, and all you get is a return value of undefined. The second constructor creates a global function object when you omit the new keyword, and of course creates new function objects for each call. You have a constructor (and a prototype) that is, in fact, idling... Which brings us to example 3

示例3:

var foo = [];//create an array - empty
console.log(foo[123]);//logs undefined.

好的,那么幕后会发生什么: foo 引用一个对象 Array 的实例,它继承了Object原型的形式(只需尝试 Object.getPrototypeOf (Array.prototype))。这是有道理的,因此一个Array实例的工作方式与任何对象几乎相同,所以:

Ok, so what happens behind the scenes: foo references an object, instance of Array, which in turn inherits form the Object prototype (just try Object.getPrototypeOf(Array.prototype)). It stands to reason, therefore that an Array instance works in pretty much the same way as any object, so:

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

In other words, a chain like you describe isn't too far-fetched or uncommon. It's how JS works, so expecting that to slow things down is like expecting your brain to fry because your thinking: yes, you can get worn out by thinking too much, but just know when to take a break. Just like in the case of prototype-chains: their great, just know that they are a tad slower, yes...

这篇关于是什么让my.class.js如此之快?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆