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