JavaScript原型操作员性能:节省内存,但速度更快吗?

我在这里阅读(道格拉斯·克罗克福德)使用原型操作符来添加方法到Javascript类还节省了内存

然后,我读到John Resig的文章 “用一堆原型属性实例化一个函数是非常非常快的 ,但是他是在谈论以标准方式使用原型,还是在他的文章中谈论他的具体示例?

例如,创build这个对象:

function Class1() { this.showMsg = function(string) { alert(string); } } var c = new Class1(); c.showMsg(); 

慢于创build这个对象,然后呢?

 function Class1() {} Class1.prototype.showMsg = function(string) { alert(string); } var c = new Class1(); c.showMsg(); 

PS

我知道原型是用来创buildinheritance和单例对象等,但这个问题与这些主题没有任何关系。


编辑:它可能感兴趣的JS对象和JS静态对象之间的性能比较可以阅读下面的答案 。 静态对象肯定是快的 ,显然只有当你不需要多于一个对象的实例时才可以使用静态对象。

这是一个有趣的问题,所以我运行了一些非常简单的testing(我应该重新启动浏览器来清除内存,但是我没有;拿这个值得)。 看起来至less在Safari和Firefox上, prototype运行速度明显加快[编辑:不像前面所述的20倍]。 我敢肯定,一个具有全function对象的真实世界的testing将是一个更好的比较。 我跑的代码是这样的(我分别跑几次testing):

 var X,Y, x,y, i, intNow; X = function() {}; X.prototype.message = function(s) { var mymessage = s + "";} X.prototype.addition = function(i,j) { return (i *2 + j * 2) / 2; } Y = function() { this.message = function(s) { var mymessage = s + "";} this.addition = function(i,j) { return (i *2 + j * 2) / 2; } }; intNow = (new Date()).getTime(); for (i = 0; i < 1000000; i++) { y = new Y(); y.message('hi'); y.addition(i,2) } console.log((new Date()).getTime() - intNow); //FF=5206ms; Safari=1554 intNow = (new Date()).getTime(); for (i = 0; i < 1000000; i++) { x = new X(); x.message('hi'); x.addition(i,2) } console.log((new Date()).getTime() - intNow);//FF=3894ms;Safari=606 

这是一个真正的耻辱,因为我真的讨厌使用prototype 。 我喜欢我的目标代码是自封装的,不允许漂移。 我想当速度很重要的时候,我没有select。 该死。

[编辑]非常感谢@Kevin谁指出我以前的代码是错误的,大大提高了prototype方法的报告速度。 固定之后,原型仍然快得多,但差别不是那么大。

我猜想这取决于你想创build的对象的types。 我运行了一个和Andrew类似的testing,但是使用了一个静态对象,静态对象赢得了胜利。 这是testing:

 var X,Y,Z,x,y,z; X = function() {}; X.prototype.message = function(s) { var mymessage = s + "";} X.prototype.addition = function(i,j) { return (i *2 + j * 2) / 2; } Y = function() { this.message = function(s) { var mymessage = s + "";} this.addition = function(i,j) { return (i *2 + j * 2) / 2; } }; Z = { message: function(s) { var mymessage = s + "";} ,addition: function(i,j) { return (i *2 + j * 2) / 2; } } function TestPerformance() { var closureStartDateTime = new Date(); for (var i = 0; i < 100000; i++) { y = new Y(); y.message('hi'); y.addition(i,2); } var closureEndDateTime = new Date(); var prototypeStartDateTime = new Date(); for (var i = 0; i < 100000; i++) { x = new X(); x.message('hi'); x.addition(i,2); } var prototypeEndDateTime = new Date(); var staticObjectStartDateTime = new Date(); for (var i = 0; i < 100000; i++) { z = Z; // obviously you don't really need this z.message('hi'); z.addition(i,2); } var staticObjectEndDateTime = new Date(); var closureTime = closureEndDateTime.getTime() - closureStartDateTime.getTime(); var prototypeTime = prototypeEndDateTime.getTime() - prototypeStartDateTime.getTime(); var staticTime = staticObjectEndDateTime.getTime() - staticObjectStartDateTime.getTime(); console.log("Closure time: " + closureTime + ", prototype time: " + prototypeTime + ", static object time: " + staticTime); } TestPerformance(); 

这个testing是我在以下代码中修改的代码:

http://blogs.msdn.com/b/kristoffer/archive/2007/02/13/javascript-prototype-versus-closure-execution-speed.aspx

结果:

IE6:closures时间:1062,原型时间:766,静态对象时间:406

IE8:closures时间:781,原型时间:406,静态对象时间:188

FF:closures时间:233,原型时间:141,静态对象时间:94

Safari:closures时间:152,原型时间:12,静态对象时间:6

Chrome:closures时间:13,原型时间:8,静态对象时间:3

从中学到的教训是,如果你不需要从同一个类中实例化许多不同的对象,那么把它创build为一个静态对象会胜任。 所以仔细想想你真的需要什么样的课程。

所以我决定testing一下。 我testing了创build时间,执行时间和内存使用情况。 我使用了Nodejs v0.8.12以及运行在启动到Windows 7的Mac Book Pro上的mochatesting框架。“快速”结果使用原型,“慢”结果使用模块模式。 我创build了每种types的对象100万,然后访问每个对象中的4个方法。 结果如下:

 c:\ABoxAbove>mocha test/test_andrew.js Fast Allocation took:170 msec ·Fast Access took:826 msec state[0] = First0 Free Memory:5006495744 ·Slow Allocation took:999 msec ·Slow Access took:599 msec state[0] = First0 Free Memory:4639649792 Mem diff:358248k Mem overhead per obj:366.845952bytes ? 4 tests complete (2.6 seconds) 

代码如下:

 var assert = require("assert"), os = require('os'); function Fast (){} Fast.prototype = { state:"", getState:function (){return this.state;}, setState:function (_state){this.state = _state;}, name:"", getName:function (){return this.name;}, setName:function (_name){this.name = _name;} }; function Slow (){ var state, name; return{ getState:function (){return this.state;}, setState:function (_state){this.state = _state;}, getName:function (){return this.name;}, setName:function (_name){this.name = _name;} }; } describe('test supposed fast prototype', function(){ var count = 1000000, i, objs = [count], state = "First", name="Test"; var ts, diff, mem; it ('should allocate a bunch of objects quickly', function (done){ ts = Date.now (); for (i = 0; i < count; ++i){objs[i] = new Fast ();} diff = Date.now () - ts; console.log ("Fast Allocation took:%d msec", diff); done (); }); it ('should access a bunch of objects quickly', function (done){ ts = Date.now (); for (i = 0; i < count; ++i){ objs[i].setState (state + i); assert (objs[i].getState () === state + i, "States should be equal"); objs[i].setName (name + i); assert (objs[i].getName () === name + i, "Names should be equal"); } diff = Date.now() - ts; console.log ("Fast Access took:%d msec", diff); console.log ("state[0] = " + objs[0].getState ()); mem = os.freemem(); console.log ("Free Memory:" + mem + "\n"); done (); }); it ('should allocate a bunch of objects slowly', function (done){ ts = Date.now (); for (i = 0; i < count; ++i){objs[i] = Slow ();} diff = Date.now() - ts; console.log ("Slow Allocation took:%d msec", diff); done (); }); it ('should access a bunch of objects slowly', function (done){ ts = Date.now (); for (i = 0; i < count; ++i){ objs[i].setState (state + i); assert (objs[i].getState () === state + i, "States should be equal"); objs[i].setName (name + i); assert (objs[i].getName () === name + i, "Names should be equal"); } diff = Date.now() - ts; console.log ("Slow Access took:%d msec", diff); console.log ("state[0] = " + objs[0].getState ()); var mem2 = os.freemem(); console.log ("Free Memory:" + mem2 + "\n"); console.log ("Mem diff:" + (mem - mem2) / 1024 + "k"); console.log ("Mem overhead per obj:" + (mem - mem2) / count + 'bytes'); done (); }); }); 

结论:这个备份了这个post中的其他人发现的内容。 如果你不断创build对象,那么原型机制显然更快。 如果你的代码花费大部分时间访问对象,那么模块模式更快。 如果你对内存的使用很敏感,原型机制每个对象使用约360个字节。

直观地说,在原型上创build函数似乎更有效率,更快速:函数只创build一次,而不是每创build一个新实例。

但是,在访问该function时,会出现性能差异。 当引用c.showMsg时,JavaScript运行时首先检查c上的属性。 如果找不到,则检查c的原型。

因此,在实例上创build属性会导致访问时间稍微快一些 – 但这可能只是一个非常深的原型层次结构的问题。

我跑了我自己的testing 。

第一个结论是,静态访问实际上比实际原型慢。 有趣的是, 这个testing的版本23有一个有缺陷的原型(variablesX),它只是一遍又一遍地返回完全重写的原型对象,当我创build我的testing时,这个原型仍然比我的“真实原型”testing。

无论如何, 答案是 :除非我的testing有缺陷,否则表明真正的原型devise是最快的。 当忽略实例化时,它或者至less等于静态对象。 实例化和私有variables的这个分配要慢得多。 我不会猜测私有variables会这么慢。

我可能会感兴趣的是,我扩展了原型对象与jQuery.extend之间,它的速度与直接分配大约相同。 当然,这个扩展不在testing本身之内。 至less这是一种规避恼人的“原型”的方法。

高分辨率浏览器性能APItesting

这里没有一个testing是利用性能API来进行高分辨率testing,所以我写了一个能够显示当前许多不同场景的最快结果,其中包括比大多数运行时其他答案都要快的2个场景。

每个类别禁食(10,000次)

  • 只能访问属性(〜0.5ms){ __proto__: Type }
  • 使用属性访问创build循环对象(<3ms)Object.create(Type)

代码使用ES6而不用babel转换来确保准确性。 它在当前的chrome中工作。 运行下面的testing来查看故障。

 function profile () { function test ( name , define , construct , { index = 0 , count = 10000 , ordinals = [ 0, 1 ] , constructPrior = false } = {} ) { performance.clearMarks() performance.clearMeasures() const symbols = { type: Symbol('type') } const marks = ( { __proto__: null , start: `${name}_start` , define: `${name}_define` , construct: `${name}_construct` , end: `${name}_end` } ) performance.mark(marks.start) let Type = define() performance.mark(marks.define) let obj = constructPrior ? construct(Type) : null do { if(!constructPrior) obj = construct(Type) if(index === 0) performance.mark(marks.construct) const measureOrdinal = ordinals.includes(index) if(measureOrdinal) performance.mark(`${name}_ordinal_${index}_pre`) obj.message('hi') obj.addition(index, 2) if(measureOrdinal) performance.mark(`${name}_ordinal_${index}_post`) } while (++index < count) performance.mark(marks.end) const measureMarks = Object.assign ( { [`${name}_define`]: [ marks.start, marks.define ] , [`${name}_construct`]: [ marks.define, marks.construct ] , [`${name}_loop`]: [ marks.construct, marks.end ] , [`${name}_total`]: [ marks.start, marks.end ] } , ordinals.reduce((reduction, i) => Object.assign(reduction, { [`${name}_ordinal_${i}`]: [ `${name}_ordinal_${i}_pre`, `${name}_ordinal_${i}_post` ] }), {}) ) Object.keys(measureMarks).forEach((key) => performance.measure(key, ...measureMarks[key])) const measures = performance.getEntriesByType('measure').map(x => Object.assign(x, { endTime: x.startTime + x.duration })) measures.sort((a, b) => a.endTime - b.endTime) const durations = measures.reduce((reduction, measure) => Object.assign(reduction, { [measure.name]: measure.duration }), {}) return ( { [symbols.type]: 'profile' , profile: name , duration: durations[`${name}_total`] , durations , measures } ) } const refs = ( { __proto__: null , message: function(s) { var mymessage = s + '' } , addition: function(i, j) { return (i *2 + j * 2) / 2 } } ) const testArgs = [ [ 'constructor' , function define() { return function Type () { this.message = refs.message this.addition = refs.addition } } , function construct(Type) { return new Type() } ] , [ 'prototype' , function define() { function Type () { } Type.prototype.message = refs.message Type.prototype.addition = refs.addition return Type } , function construct(Type) { return new Type() } ] , [ 'Object.create' , function define() { return ( { __proto__: null , message: refs.message , addition: refs.addition } ) } , function construct(Type) { return Object.create(Type) } ] , [ 'proto' , function define() { return ( { __proto__: null , message: refs.message , addition: refs.addition } ) } , function construct(Type) { return { __proto__: Type } } ] ] return testArgs.reduce( (reduction, [ name, ...args ]) => ( Object.assign( reduction , { [name]: ( { normal: test(name, ...args, { constructPrior: true }) , reconstruct: test(`${name}_reconstruct`, ...args, { constructPrior: false }) } ) } ) ) , {}) } let profiled = profile() const breakdown = Object.keys(profiled).reduce((reduction, name) => [ ...reduction, ...Object.keys(profiled[name]).reduce((r, type) => [ ...r, { profile: `${name}_${type}`, duration: profiled[name][type].duration } ], []) ], []) breakdown.sort((a, b) => a.duration - b.duration) try { const Pre = props => React.createElement('pre', { children: JSON.stringify(props.children, null, 2) }) ReactDOM.render(React.createElement(Pre, { children: { breakdown, profiled } }), document.getElementById('profile')) } catch(err) { console.error(err) } 
 <script src="ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id="profile"></div> 

我们需要分离对象的构build和使用。

在原型上声明函数时,它将在所有实例之间共享。 在构造函数中声明一个函数时,每次创build新实例时都会重新创build这个函数。 鉴于此,我们需要分别build设和使用的基准,以取得更好的结果。 这就是我所做的,并希望与您分享结果。 这个基准不testing施工速度。

 function ThisFunc() { this.value = 0; this.increment = function(){ this.value++; } } function ProtFunc() { this.value = 0; } ProtFunc.prototype.increment = function (){ this.value++; } function ClosFunc() { var value = 0; return { increment:function(){ value++; } }; } var thisInstance = new ThisFunc; var iterations = 1000000; var intNow = (new Date()).getTime(); for (i = 0; i < iterations; i++) { thisInstance.increment(); } console.log(`ThisFunc: ${(new Date()).getTime() - intNow}`); // 27ms node v4.6.0 var protInstance = new ProtFunc; intNow = (new Date()).getTime(); for (i = 0; i < iterations; i++) { protInstance.increment(); } console.log(`ProtFunc: ${(new Date()).getTime() - intNow}`); // 4ms node v4.6.0 var closInstance = ClosFunc(); intNow = (new Date()).getTime(); for (i = 0; i < iterations; i++) { closInstance.increment(); } console.log(`ClosFunc: ${(new Date()).getTime() - intNow}`); // 7ms node v4.6.0 

从这些结果我们可以看出原型版本是最快的(4ms),但是封闭版本非常接近(7ms)。 您可能仍需要针对您的特定情况进行基准testing。

所以:

  • 我们可以使用原型版本,当我们需要性能或实例之间共享function的每一位。
  • 当我们想要的是他们提供的function时,我们可以使用其他版本。 (私有状态封装,可读性等)

PS:我用安德鲁的答案作为参考。 使用相同的循环和符号。

我敢肯定,就实例化的对象,它的速度更快,也消耗更less的内存,毫无疑问,但我认为,JavaScript引擎需要遍历对象的所有属性,以确定是否属性/方法被调用是该对象的一部分,如果没有,然后去检查原型。 我不是100%肯定这一点,但我假设它是如何工作的,如果是这样,那么在某些情况下,你的对象有很多方法添加到它,只实例化一次,并使用很多,那么它可能是一个慢一点,但这只是一个假设,我没有testing任何东西。

但最后我还是会认为,作为一般规则,使用原型会更快。

因此,在实例上创build属性会导致访问时间稍微快一些 – 但这可能只是一个非常深的原型层次结构的问题。

其实结果是不同的,我们可以预期 – 访问原型方法的时间更快,然后访问方法完全附加到对象(FFtesting)。