在构造函数中通过原型和使用原型定义方法——真的是性能差异吗?

时间:2022-03-03 15:07:08

In JavaScript, we have two ways of making a "class" and giving it public functions.

在JavaScript中,我们有两种方法来创建“类”并为它提供公共函数。

Method 1:

方法1:

function MyClass() {
    var privateInstanceVariable = 'foo';
    this.myFunc = function() { alert(privateInstanceVariable ); }
}

Method 2:

方法2:

function MyClass() { }

MyClass.prototype.myFunc = function() { 
    alert("I can't use private instance variables. :("); 
}

I've read numerous times people saying that using Method 2 is more efficient as all instances share the same copy of the function rather than each getting their own. Defining functions via the prototype has a huge disadvantage though - it makes it impossible to have private instance variables.

我读过很多次人们说使用方法2更有效,因为所有实例共享相同的函数副本,而不是每个实例都有自己的副本。通过原型定义函数有一个巨大的缺点——不可能有私有实例变量。

Even though, in theory, using Method 1 gives each instance of an object its own copy of the function (and thus uses way more memory, not to mention the time required for allocations) - is that what actually happens in practice? It seems like an optimization web browsers could easily make is to recognize this extremely common pattern, and actually have all instances of the object reference the same copy of functions defined via these "constructor functions". Then it could only give an instance its own copy of the function if it is explicitly changed later on.

即使在理论上,使用方法1为一个对象的每个实例提供了它自己的函数副本(因此使用了更多的内存,更不用说分配所需的时间了),但实际情况是这样吗?看起来,一个优化的web浏览器很容易就能识别这种非常常见的模式,并且实际上所有的对象实例都引用了通过这些“构造函数函数”定义的相同的函数。然后,只有在稍后显式地更改时,它才能给实例自己的函数副本。

Any insight - or, even better, real world experience - about performance differences between the two, would be extremely helpful.

任何关于两者之间性能差异的见解——或者更好的现实经验——都将非常有用。

6 个解决方案

#1


58  

See http://jsperf.com/prototype-vs-this

参见http://jsperf.com/prototype-vs-this

Declaring your methods via the prototype is faster, but whether or not this is relevant is debatable.

通过原型声明方法更快,但这是否相关还有待商榷。

If you have a performance bottleneck in your app it is unlikely to be this, unless you happen to be instantiating 10000+ objects on every step of some arbitrary animation, for example.

如果您的应用程序存在性能瓶颈,那么不太可能出现这种情况,除非您碰巧在任意动画的每个步骤上实例化10000+对象,例如。

If performance is a serious concern, and you'd like to micro-optimise, then I would suggest declaring via prototype. Otherwise, just use the pattern that makes most sense to you.

如果性能是一个严重的问题,并且您希望进行微优化,那么我建议通过prototype声明。否则,只需要使用对你最有意义的模式。

I'll add that, in JavaScript, there is a convention of prefixing properties that are intended to be seen as private with an underscore (e.g. _process()). Most developers will understand and avoid these properties, unless they're willing to forgo the social contract, but in that case you might as well not cater to them. What I mean to say is that: you probably don't really need true private variables...

我还要补充一点,在JavaScript中,有一种习惯,即在属性前面加上一个下划线(例如_process()))。大多数开发人员会理解并避免这些属性,除非他们愿意放弃社会契约,但在这种情况下,你最好不要迎合他们。我想说的是:你可能不需要真正的私人变量……

#2


2  

In the new version of Chrome, this.method is about 20% faster than prototype.method, but creating new object is still slower.

在Chrome的新版本中,这个。方法比原型快20%左右。方法,但是创建新对象仍然较慢。

If you can reuse the object instead of always creating an new one, this can be 50% - 90% faster than creating new objects. Plus the benefit of no garbage collection, which is huge:

如果您可以重用对象而不是总是创建一个新对象,那么这将比创建新对象快50% - 90%。加上没有垃圾收集的好处,这是巨大的:

http://jsperf.com/prototype-vs-this/59

http://jsperf.com/prototype-vs-this/59

#3


1  

It only makes a difference when you're creating lots of instances. Otherwise, the performance of calling the member function is exactly the same in both cases.

它只在创建大量实例时起作用。否则,在这两种情况下调用成员函数的性能是完全相同的。

I've created a test case on jsperf to demonstrate this:

我在jsperf上创建了一个测试用例来演示这个:

http://jsperf.com/prototype-vs-this/10

http://jsperf.com/prototype-vs-this/10

#4


1  

You might not have considered this, but putting the method directly on the object is actually better in one way:

你可能没有考虑到这一点,但将方法直接放在对象上实际上在某种程度上更好:

  1. Method invocations are very slightly faster (jsperf) since the prototype chain does not have to be consulted to resolve the method.
  2. 方法调用要稍微快一点(jsperf),因为不必参考原型链来解析方法。

However, the speed difference is almost negligible. On top of that, putting a method on a prototype is better in two more impactful ways:

然而,速度上的差异几乎可以忽略不计。最重要的是,将一种方法放在原型上有两种更有效的方式:

  1. Faster to create instances (jsperf)
  2. 更快地创建实例(jsperf)
  3. Uses less memory
  4. 使用更少的内存

Like James said, this difference can be important if you are instantiating thousands of instances of a class.

就像James说的,如果要实例化一个类的数千个实例,这种差异是很重要的。

That said, I can certainly imagine a JavaScript engine that recognizes that the function you are attaching to each object does not change across instances and thus only keeps one copy of the function in memory, with all instance methods pointing to the shared function. In fact, it seems that Firefox is doing some special optimization like this but Chrome is not.

也就是说,我可以想象一个JavaScript引擎,它可以识别您附加到每个对象上的函数不会在实例之间发生变化,因此只能在内存中保留函数的一个副本,所有实例方法都指向共享函数。事实上,火狐似乎在做一些特殊的优化,但Chrome不是。


ASIDE:

旁白:

You are right that it is impossible to access private instance variables from inside methods on prototypes. So I guess the question you must ask yourself is do you value being able to make instance variables truly private over utilizing inheritance and prototyping? I personally think that making variables truly private is not that important and would just use the underscore prefix (e.g., "this._myVar") to signify that although the variable is public, it should be considered to be private. That said, in ES6, there is apparently a way to have the both of both worlds!

您是对的,从原型的内部方法访问私有实例变量是不可能的。所以我猜你必须问自己的问题是你是否重视使实例变量变得真正私有而不是利用继承和原型?我个人认为,使变量真正私有并不重要,只需使用下划线前缀(例如,“this._myVar”)就可以表明,尽管变量是公共的,但它应该被视为私有的。也就是说,在ES6中,显然有一种方法可以同时拥有这两个世界!

#5


0  

In short, use method 2 for creating properties/methods that all instances will share. Those will be "global" and any change to it will be reflected across all instances. Use method 1 for creating instance specific properties/methods.

简而言之,使用方法2创建所有实例将共享的属性/方法。这些将是“全球性的”,任何改变都将在所有实例中反映出来。使用方法1创建实例特定的属性/方法。

I wish I had a better reference but for now take a look at this. You can see how I used both methods in the same project for different purposes.

我希望我有更好的参考,但现在看看这个。您可以看到我如何在同一个项目中为不同的目的使用这两个方法。

Hope this helps. :)

希望这个有帮助。:)

#6


0  

This answer should be considered an expansion of the rest of the answers filling in missing points. Both personal experience and benchmarks are incorporated.

这个答案应该被认为是对其他答案的扩展,填补了缺失的点。包括个人经验和基准。

As far as my experience goes, I use constructors to literally construct my objects religiously, whether methods are private or not. The main reason being that when I started that was the easiest immediate approach to me so it's not a special preference. It might have been as simple as that I like visible encapsulation and prototypes are a bit disembodied. My private methods will be assigned as variables in the scope as well. Although this is my habit and keeps things nicely self contained, it's not always the best habit and I do sometimes hit walls. Apart from wacky scenarios with highly dynamic self assembling according to configuration objects and code layout it tends to be the weaker approach in my opinion particularly if performance is a concern. Knowing that the internals are private is useful but you can achieve that via other means with the right discipline. Unless performance is a serious consideration, use whatever works best otherwise for the task at hand.

就我的经验而言,无论方法是否私有,我都使用构造函数严格地构造对象。主要的原因是当我开始的时候这对我来说是最简单的直接方法所以这不是一个特殊的偏好。它可能很简单,就像我喜欢可见的封装和原型有点脱离实际一样。我的私有方法也将被分配为作用域中的变量。虽然这是我的习惯,能很好地克制自己,但这并不总是最好的习惯,我有时也会遇到困难。除了根据配置对象和代码布局进行高度动态自组装的古怪场景外,在我看来,这是一种较弱的方法,尤其是在性能方面。知道内部人是私人的是有用的,但是你可以通过其他方式通过正确的纪律来达到这个目的。除非性能是一个认真的考虑,否则就使用其他最好的方法来完成手边的任务。

  1. Using prototype inheritance and a convention to mark items as private does make debugging easier as you can then traverse the object graph easily from the console or debugger. On the other hand, such a convention makes obfuscation somewhat harder and makes it easier for others to bolt on their own scripts onto your site. This is one of the reasons the private scope approach gained popularity. It's not true security but instead adds resistance. Unfortunately a lot of people do still think it's a genuinely way to program secure JavaScript. Since debuggers have gotten really good, code obfuscation takes its place. If you're looking for security flaws where too much is on the client, it's a design pattern your might want to look out for.
  2. 使用原型继承和约定将项目标记为private确实使调试更容易,因为您可以从控制台或调试器轻松地遍历对象图。另一方面,这样的约定会使混淆变得更加困难,并使其他人更容易将自己的脚本硬塞到您的站点上。这就是私有范围方法流行的原因之一。这不是真正的安全,而是增加了阻力。不幸的是,很多人仍然认为这是一种真正的编程安全JavaScript的方式。因为调试器已经变得非常好了,代码混淆就取而代之了。如果你在寻找太多客户端存在的安全缺陷,这是你可能想要寻找的设计模式。
  3. A convention allows you to have protected properties with little fuss. That can be a blessing and a curse. It does ease some inheritance issues as it is less restrictive. You still do have the risk of collision or increased cognitive load in considering where else a property might be accessed. Self assembling objects let you do some strange things where you can get around a number of inheritance problems but they can be unconventional. My modules tend to have a rich inner structure where things don't get pulled out until the functionality is needed elsewhere (shared) or exposed unless needed externally. The constructor pattern tends to lead to creating self contained sophisticated modules more so than simply piecemeal objects. If you want that then it's fine. Otherwise if you want a more traditional OOP structure and layout then I would probably suggest regulating access by convention. In my usage scenarios complex OOP isn't often justified and modules do the trick.
  4. 一种约定允许您轻松地拥有受保护的属性。这可能是一种祝福,也可能是一种诅咒。它确实缓解了一些继承问题,因为它限制较少。在考虑属性可能被访问的其他地方时,仍然存在碰撞或增加认知负担的风险。自组装对象让你做一些奇怪的事情,你可以解决一些继承问题,但它们可以是非常规的。我的模块通常有一个丰富的内部结构,在这个结构中,除非需要外部的需要,否则在其他地方(共享)或公开的功能是需要的。构造函数模式倾向于创建自包含的复杂模块,而不是简单的片段对象。如果你想要,那没关系。否则,如果您想要更传统的OOP结构和布局,那么我可能会建议按照约定来规范访问。在我的使用场景中,复杂的OOP通常是不合理的,而模块可以做到这一点。
  5. All of the tests here are minimal. In real world usage it is likely that modules will be more complex making the hit a lot greater than tests here will indicate. It's quite common to have a private variable with multiple methods working on it and each of those methods will add more overhead on initialisation that you wont get with prototype inheritance. In most cases is doesn't matter because only a few instances of such objects float around although cumulatively it might add up.
  6. 这里所有的测试都是最小的。在实际应用中,模块可能会比这里的测试所显示的要复杂得多。使用具有多个方法的私有变量是很常见的,而且每个方法都会增加初始化的开销,这是原型继承所不能提供的。在大多数情况下,这并不重要,因为只有很少的这样的对象实例是浮动的,尽管它可能会累积起来。
  7. There is an assumption that prototype methods are slower to call because of prototype lookup. It's not an unfair assumption, I made the same myself until I tested it. In reality it's complex and some tests suggest that aspect is trivial. Between, prototype.m = f, this.m = f and this.m = function... the latter performs significantly better than the first two which perform around the same. If the prototype lookup alone were a significant issue then the last two functions instead would out perform the first significantly. Instead something else strange is going on at least where Canary is concerned. It's possible functions are optimised according to what they are members of. A multitude of performance considerations come into play. You also have differences for parameter access and variable access.
  8. 有一种假设,由于原型查找,原型方法调用速度较慢。这并不是不公平的假设,我自己做了同样的假设直到我测试它。在现实中,它是复杂的,一些测试表明方面是微不足道的。之间,原型。m = f。m = f和这个。m =函数…后者的表现要比前两个表现相同的表现要好得多。如果原型查找本身就是一个重要的问题,那么最后两个函数将会显著地执行第一个。相反,奇怪的事情正在发生,至少金丝雀是这样。可以根据函数的成员进行优化。大量的性能考虑因素开始发挥作用。对于参数访问和变量访问,您也有不同之处。
  9. Memory Capacity. It's not well discussed here. An assumption you can make up front that's likely to be true is that prototype inheritance will usually be far more memory efficient and according to my tests it is in general. When you build up your object in your constructor you can assume that each object will probably have its own instance of each function rather than shared, a larger property map for its own personal properties and likely some overhead to keep the constructor scope open as well. Functions that operate on the private scope are extremely and disproportionately demanding of memory. I find that in a lot of scenarios the proportionate difference in memory will be much more significant than the proportionate difference in CPU cycles.
  10. 内存容量。这里没有好好讨论。一个你可以预先做出的假设很可能是对的,原型继承通常会比我的测试要有效得多。当您在构造函数中构建对象时,您可以假设每个对象可能都有每个函数的自己的实例,而不是共享的,有一个更大的属性映射,以及保持构造函数作用域开放的一些开销。在私有范围上操作的函数对内存的要求极其苛刻。我发现在很多情况下,内存的比例差异比CPU周期的比例差异要重要得多。
  11. Memory Graph. You also can jam up the engine making GC more expensive. Profilers do tend to show time spent in GC these days. It's not only a problem when it comes to allocating and freeing more. You'll also create a larger object graph to traverse and things like that so the GC consumes more cycles. If you create a million objects and then hardly touch them, depending on the engine it might turn out to have more of an ambient performance impact than you expected. I have proven that this does at least make the gc run for longer when objects are disposed of. That is there tends to be a correlation with memory used and the time it takes to GC. However there are cases where the time is the same regardless of the memory. This indicates that the graph makeup (layers of indirection, item count, etc) has more impact. That's not something that is always easy to predict.
  12. 内存图。您还可以阻塞引擎,使GC更贵。分析器确实倾向于显示在GC中花费的时间。这不仅仅是分配和释放更多资源的问题。您还将创建一个更大的要遍历的对象图和类似的东西,以便GC消耗更多的周期。如果您创建了一百万个对象,然后几乎不去碰它们,那么根据引擎的不同,可能会产生比您预期的更多的环境性能影响。我已经证明,这至少可以使gc在处理对象时运行更长时间。也就是说,与所使用的内存和花在GC上的时间有相关性。然而,在某些情况下,不管内存大小,时间都是相同的。这表明图形构成(间接层、项数等)具有更大的影响。这并不总是容易预测的。
  13. Not many people use chained prototypes extensively, myself included I have to admit. Prototype chains can be expensive in theory. Someone will but I've not measured the cost. If you instead build your objects entirely in the constructor and then have a chain of inheritance as each constructor calls a parent constructor upon itself, in theory method access should be much faster. On the other hand you can accomplish the equivalent if it matters (such as flatten the prototypes down the ancestor chain) and you don't mind breaking things like hasOwnProperty, perhaps instanceof, etc if you really need it. In either case things start to get complex once you down this road when it comes to performance hacks. You'll probably end up doing things you shouldn't be doing.
  14. 没有多少人广泛地使用链式原型,包括我自己在内,我不得不承认。理论上,原型链是昂贵的。有人会的,但我还没计算成本。如果您完全在构造函数中构建对象,然后在每个构造函数调用父构造函数时拥有一个继承链,那么理论上,方法访问应该要快得多。另一方面,如果它很重要(比如在祖先链上把原型压平),你也可以完成相应的任务,而且你不介意破坏像hasOwnProperty,或者instanceof之类的东西,如果你真的需要它的话。在这两种情况下,一旦遇到性能破坏,事情就会变得复杂起来。你可能会做一些不该做的事情。
  15. Many people don't directly use either approach you've presented. Instead they make their own things using anonymous objects allowing method sharing any which way (mixins for example). There are a number of frameworks as well that implement their own strategies for organising modules and objects. These are heavily convention based custom approaches. For most people and for you your first challenge should be organisation rather than performance. This is often complicated in that Javascript gives many ways of achieving things versus languages or platforms with more explicit OOP/namespace/module support. When it comes to performance I would say instead to avoid major pitfalls first and foremost.
  16. 许多人不会直接使用你所提出的任何一种方法。相反,它们使用匿名对象创建自己的东西,允许方法以任何方式共享(例如mixin)。也有许多框架实现了它们自己的组织模块和对象的策略。这些都是基于大量约定的自定义方法。对于大多数人和你来说,你的第一个挑战应该是组织而不是表现。这通常是很复杂的,因为Javascript提供了许多实现事物的方法,而不是具有更明确的OOP/名称空间/模块支持的语言或平台。当谈到性能时,我想说的是,首先要避免主要的缺陷。
  17. There's a new Symbol type that's supposed to work for private variables and methods. There are a number of ways to use this and it raises a host of questions related to performance and access. In my tests the performance of Symbols wasn't great compared to everything else but I never tested them thoroughly.
  18. 有一个新的符号类型,它应该适用于私有变量和方法。有许多方法可以使用它,并且它引发了许多与性能和访问相关的问题。在我的测试中,与其他东西相比,符号的性能并不是很好,但我从来没有对它们进行过彻底的测试。

Disclaimers:

免责声明:

  1. There are lots of discussions about performance and there isn't always a permanently correct answer for this as usage scenarios and engines change. Always profile but also always measure in more than one way as profiles aren't always accurate or reliable. Avoid significant effort into optimisation unless there's definitely a demonstrable problem.
  2. 有很多关于性能的讨论,并且随着使用场景和引擎的改变,并不总是有一个永久正确的答案。通常情况下,配置文件并不总是准确或可靠的,但也总是以多种方式测量。避免在优化方面做出重大努力,除非确实存在明显的问题。
  3. It's probably better instead to include performance checks for sensitive areas in automated testing and to run when browsers update.
  4. 最好在自动测试中包含对敏感区域的性能检查,并在浏览器更新时运行。
  5. Remember sometimes battery life matters as well as perceptible performance. The slowest solution might turn out faster after running an optimising compiler on it (IE, a compiler might have a better idea of when restricted scope variables are accessed than properties marked as private by convention). Consider backend such as node.js. This can require better latency and throughput than you would often find on the browser. Most people wont need to worry about these things with something like validation for a registration form but the number of diverse scenarios where such things might matter is growing.
  6. 记住,有时电池寿命和可感知性能一样重要。在上面运行优化编译器之后,最慢的解决方案可能会运行得更快(例如,编译器可能更清楚何时访问受限制的范围变量,而不是按照约定标记为私有的属性)。考虑后端,如node.js。这可能需要比浏览器更好的延迟和吞吐量。大多数人不需要担心诸如注册表的验证之类的事情,但是这些事情可能很重要的不同场景的数量正在增加。
  7. You have to be careful with memory allocation tracking tools in to persist the result. In some cases where I didn't return and persist the data it was optimised out entirely or the sample rate was not sufficient between instantiated/unreferenced, leaving me scratching my head as to how an array initialised and filled to a million registered as 3.4KiB in the allocation profile.
  8. 您必须小心使用内存分配跟踪工具来持久化结果。在某些情况下,当我没有返回并保存数据时,它完全被优化了,或者在实例化/未引用的情况下,样本速率是不够的,这让我对数组初始化和在分配配置文件中被填入的100万注册为3.4KiB感到困惑。
  9. In the real world in most cases the only way to really optimise an application is to write it in the first place so you can measure it. There are dozens to hundreds of factors that can come into play if not thousands in any given scenario. Engines also do things that can lead to asymmetric or non-linear performance characteristics. If you define functions in a constructor, they might be arrow functions or traditional, each behaves differently in certain situations and I have no idea about the other function types. Classes also don't behave the same in terms as performance for prototyped constructors that should be equivalent. You need to be really careful with benchmarks as well. Prototyped classes can have deferred initialisation in various ways, especially if your prototyped your properties as well (advice, don't). This means that you can understate initialisation cost and overstate access/property mutation cost. I have also seen indications of progressive optimisation. In these cases I have filled a large array with instances of objects that are identical and as the number of instances increase the objects appear to be incrementally optimised for memory up to a point where the remainder is the same. It is also possible that those optimisations can also impact CPU performance significantly. These things are heavily dependent not merely on the code you write but what happens in runtime such as number of objects, variance between objects, etc.
  10. 在现实世界中,在大多数情况下,真正优化应用程序的唯一方法是首先编写它,以便您可以度量它。在任何给定的场景中,都有数十到数百个因素可以发挥作用,如果不是数千个的话。引擎也会做一些会导致不对称或非线性性能的事情。如果在构造函数中定义函数,它们可能是箭头函数或传统函数,它们在某些情况下的行为都不同,我不知道其他函数类型。类的行为在性能上也不与应该等效的原型构造函数相同。对于基准,您也需要非常小心。原型类可以以各种方式进行延迟初始化,特别是如果您的属性也进行了原型化(建议不要这样做)。这意味着您可以低估初始化成本和超状态访问/属性突变成本。我也看到了渐进式优化的迹象。在这些情况下,我已经在一个大数组中填充了相同的对象实例,并且随着实例数量的增加,对象似乎会逐渐优化,以使内存达到与其他对象相同的点。这些优化还可能显著影响CPU性能。这些东西不仅严重依赖于您编写的代码,而且还依赖于运行时发生的事情,比如对象的数量、对象之间的差异等等。

#1


58  

See http://jsperf.com/prototype-vs-this

参见http://jsperf.com/prototype-vs-this

Declaring your methods via the prototype is faster, but whether or not this is relevant is debatable.

通过原型声明方法更快,但这是否相关还有待商榷。

If you have a performance bottleneck in your app it is unlikely to be this, unless you happen to be instantiating 10000+ objects on every step of some arbitrary animation, for example.

如果您的应用程序存在性能瓶颈,那么不太可能出现这种情况,除非您碰巧在任意动画的每个步骤上实例化10000+对象,例如。

If performance is a serious concern, and you'd like to micro-optimise, then I would suggest declaring via prototype. Otherwise, just use the pattern that makes most sense to you.

如果性能是一个严重的问题,并且您希望进行微优化,那么我建议通过prototype声明。否则,只需要使用对你最有意义的模式。

I'll add that, in JavaScript, there is a convention of prefixing properties that are intended to be seen as private with an underscore (e.g. _process()). Most developers will understand and avoid these properties, unless they're willing to forgo the social contract, but in that case you might as well not cater to them. What I mean to say is that: you probably don't really need true private variables...

我还要补充一点,在JavaScript中,有一种习惯,即在属性前面加上一个下划线(例如_process()))。大多数开发人员会理解并避免这些属性,除非他们愿意放弃社会契约,但在这种情况下,你最好不要迎合他们。我想说的是:你可能不需要真正的私人变量……

#2


2  

In the new version of Chrome, this.method is about 20% faster than prototype.method, but creating new object is still slower.

在Chrome的新版本中,这个。方法比原型快20%左右。方法,但是创建新对象仍然较慢。

If you can reuse the object instead of always creating an new one, this can be 50% - 90% faster than creating new objects. Plus the benefit of no garbage collection, which is huge:

如果您可以重用对象而不是总是创建一个新对象,那么这将比创建新对象快50% - 90%。加上没有垃圾收集的好处,这是巨大的:

http://jsperf.com/prototype-vs-this/59

http://jsperf.com/prototype-vs-this/59

#3


1  

It only makes a difference when you're creating lots of instances. Otherwise, the performance of calling the member function is exactly the same in both cases.

它只在创建大量实例时起作用。否则,在这两种情况下调用成员函数的性能是完全相同的。

I've created a test case on jsperf to demonstrate this:

我在jsperf上创建了一个测试用例来演示这个:

http://jsperf.com/prototype-vs-this/10

http://jsperf.com/prototype-vs-this/10

#4


1  

You might not have considered this, but putting the method directly on the object is actually better in one way:

你可能没有考虑到这一点,但将方法直接放在对象上实际上在某种程度上更好:

  1. Method invocations are very slightly faster (jsperf) since the prototype chain does not have to be consulted to resolve the method.
  2. 方法调用要稍微快一点(jsperf),因为不必参考原型链来解析方法。

However, the speed difference is almost negligible. On top of that, putting a method on a prototype is better in two more impactful ways:

然而,速度上的差异几乎可以忽略不计。最重要的是,将一种方法放在原型上有两种更有效的方式:

  1. Faster to create instances (jsperf)
  2. 更快地创建实例(jsperf)
  3. Uses less memory
  4. 使用更少的内存

Like James said, this difference can be important if you are instantiating thousands of instances of a class.

就像James说的,如果要实例化一个类的数千个实例,这种差异是很重要的。

That said, I can certainly imagine a JavaScript engine that recognizes that the function you are attaching to each object does not change across instances and thus only keeps one copy of the function in memory, with all instance methods pointing to the shared function. In fact, it seems that Firefox is doing some special optimization like this but Chrome is not.

也就是说,我可以想象一个JavaScript引擎,它可以识别您附加到每个对象上的函数不会在实例之间发生变化,因此只能在内存中保留函数的一个副本,所有实例方法都指向共享函数。事实上,火狐似乎在做一些特殊的优化,但Chrome不是。


ASIDE:

旁白:

You are right that it is impossible to access private instance variables from inside methods on prototypes. So I guess the question you must ask yourself is do you value being able to make instance variables truly private over utilizing inheritance and prototyping? I personally think that making variables truly private is not that important and would just use the underscore prefix (e.g., "this._myVar") to signify that although the variable is public, it should be considered to be private. That said, in ES6, there is apparently a way to have the both of both worlds!

您是对的,从原型的内部方法访问私有实例变量是不可能的。所以我猜你必须问自己的问题是你是否重视使实例变量变得真正私有而不是利用继承和原型?我个人认为,使变量真正私有并不重要,只需使用下划线前缀(例如,“this._myVar”)就可以表明,尽管变量是公共的,但它应该被视为私有的。也就是说,在ES6中,显然有一种方法可以同时拥有这两个世界!

#5


0  

In short, use method 2 for creating properties/methods that all instances will share. Those will be "global" and any change to it will be reflected across all instances. Use method 1 for creating instance specific properties/methods.

简而言之,使用方法2创建所有实例将共享的属性/方法。这些将是“全球性的”,任何改变都将在所有实例中反映出来。使用方法1创建实例特定的属性/方法。

I wish I had a better reference but for now take a look at this. You can see how I used both methods in the same project for different purposes.

我希望我有更好的参考,但现在看看这个。您可以看到我如何在同一个项目中为不同的目的使用这两个方法。

Hope this helps. :)

希望这个有帮助。:)

#6


0  

This answer should be considered an expansion of the rest of the answers filling in missing points. Both personal experience and benchmarks are incorporated.

这个答案应该被认为是对其他答案的扩展,填补了缺失的点。包括个人经验和基准。

As far as my experience goes, I use constructors to literally construct my objects religiously, whether methods are private or not. The main reason being that when I started that was the easiest immediate approach to me so it's not a special preference. It might have been as simple as that I like visible encapsulation and prototypes are a bit disembodied. My private methods will be assigned as variables in the scope as well. Although this is my habit and keeps things nicely self contained, it's not always the best habit and I do sometimes hit walls. Apart from wacky scenarios with highly dynamic self assembling according to configuration objects and code layout it tends to be the weaker approach in my opinion particularly if performance is a concern. Knowing that the internals are private is useful but you can achieve that via other means with the right discipline. Unless performance is a serious consideration, use whatever works best otherwise for the task at hand.

就我的经验而言,无论方法是否私有,我都使用构造函数严格地构造对象。主要的原因是当我开始的时候这对我来说是最简单的直接方法所以这不是一个特殊的偏好。它可能很简单,就像我喜欢可见的封装和原型有点脱离实际一样。我的私有方法也将被分配为作用域中的变量。虽然这是我的习惯,能很好地克制自己,但这并不总是最好的习惯,我有时也会遇到困难。除了根据配置对象和代码布局进行高度动态自组装的古怪场景外,在我看来,这是一种较弱的方法,尤其是在性能方面。知道内部人是私人的是有用的,但是你可以通过其他方式通过正确的纪律来达到这个目的。除非性能是一个认真的考虑,否则就使用其他最好的方法来完成手边的任务。

  1. Using prototype inheritance and a convention to mark items as private does make debugging easier as you can then traverse the object graph easily from the console or debugger. On the other hand, such a convention makes obfuscation somewhat harder and makes it easier for others to bolt on their own scripts onto your site. This is one of the reasons the private scope approach gained popularity. It's not true security but instead adds resistance. Unfortunately a lot of people do still think it's a genuinely way to program secure JavaScript. Since debuggers have gotten really good, code obfuscation takes its place. If you're looking for security flaws where too much is on the client, it's a design pattern your might want to look out for.
  2. 使用原型继承和约定将项目标记为private确实使调试更容易,因为您可以从控制台或调试器轻松地遍历对象图。另一方面,这样的约定会使混淆变得更加困难,并使其他人更容易将自己的脚本硬塞到您的站点上。这就是私有范围方法流行的原因之一。这不是真正的安全,而是增加了阻力。不幸的是,很多人仍然认为这是一种真正的编程安全JavaScript的方式。因为调试器已经变得非常好了,代码混淆就取而代之了。如果你在寻找太多客户端存在的安全缺陷,这是你可能想要寻找的设计模式。
  3. A convention allows you to have protected properties with little fuss. That can be a blessing and a curse. It does ease some inheritance issues as it is less restrictive. You still do have the risk of collision or increased cognitive load in considering where else a property might be accessed. Self assembling objects let you do some strange things where you can get around a number of inheritance problems but they can be unconventional. My modules tend to have a rich inner structure where things don't get pulled out until the functionality is needed elsewhere (shared) or exposed unless needed externally. The constructor pattern tends to lead to creating self contained sophisticated modules more so than simply piecemeal objects. If you want that then it's fine. Otherwise if you want a more traditional OOP structure and layout then I would probably suggest regulating access by convention. In my usage scenarios complex OOP isn't often justified and modules do the trick.
  4. 一种约定允许您轻松地拥有受保护的属性。这可能是一种祝福,也可能是一种诅咒。它确实缓解了一些继承问题,因为它限制较少。在考虑属性可能被访问的其他地方时,仍然存在碰撞或增加认知负担的风险。自组装对象让你做一些奇怪的事情,你可以解决一些继承问题,但它们可以是非常规的。我的模块通常有一个丰富的内部结构,在这个结构中,除非需要外部的需要,否则在其他地方(共享)或公开的功能是需要的。构造函数模式倾向于创建自包含的复杂模块,而不是简单的片段对象。如果你想要,那没关系。否则,如果您想要更传统的OOP结构和布局,那么我可能会建议按照约定来规范访问。在我的使用场景中,复杂的OOP通常是不合理的,而模块可以做到这一点。
  5. All of the tests here are minimal. In real world usage it is likely that modules will be more complex making the hit a lot greater than tests here will indicate. It's quite common to have a private variable with multiple methods working on it and each of those methods will add more overhead on initialisation that you wont get with prototype inheritance. In most cases is doesn't matter because only a few instances of such objects float around although cumulatively it might add up.
  6. 这里所有的测试都是最小的。在实际应用中,模块可能会比这里的测试所显示的要复杂得多。使用具有多个方法的私有变量是很常见的,而且每个方法都会增加初始化的开销,这是原型继承所不能提供的。在大多数情况下,这并不重要,因为只有很少的这样的对象实例是浮动的,尽管它可能会累积起来。
  7. There is an assumption that prototype methods are slower to call because of prototype lookup. It's not an unfair assumption, I made the same myself until I tested it. In reality it's complex and some tests suggest that aspect is trivial. Between, prototype.m = f, this.m = f and this.m = function... the latter performs significantly better than the first two which perform around the same. If the prototype lookup alone were a significant issue then the last two functions instead would out perform the first significantly. Instead something else strange is going on at least where Canary is concerned. It's possible functions are optimised according to what they are members of. A multitude of performance considerations come into play. You also have differences for parameter access and variable access.
  8. 有一种假设,由于原型查找,原型方法调用速度较慢。这并不是不公平的假设,我自己做了同样的假设直到我测试它。在现实中,它是复杂的,一些测试表明方面是微不足道的。之间,原型。m = f。m = f和这个。m =函数…后者的表现要比前两个表现相同的表现要好得多。如果原型查找本身就是一个重要的问题,那么最后两个函数将会显著地执行第一个。相反,奇怪的事情正在发生,至少金丝雀是这样。可以根据函数的成员进行优化。大量的性能考虑因素开始发挥作用。对于参数访问和变量访问,您也有不同之处。
  9. Memory Capacity. It's not well discussed here. An assumption you can make up front that's likely to be true is that prototype inheritance will usually be far more memory efficient and according to my tests it is in general. When you build up your object in your constructor you can assume that each object will probably have its own instance of each function rather than shared, a larger property map for its own personal properties and likely some overhead to keep the constructor scope open as well. Functions that operate on the private scope are extremely and disproportionately demanding of memory. I find that in a lot of scenarios the proportionate difference in memory will be much more significant than the proportionate difference in CPU cycles.
  10. 内存容量。这里没有好好讨论。一个你可以预先做出的假设很可能是对的,原型继承通常会比我的测试要有效得多。当您在构造函数中构建对象时,您可以假设每个对象可能都有每个函数的自己的实例,而不是共享的,有一个更大的属性映射,以及保持构造函数作用域开放的一些开销。在私有范围上操作的函数对内存的要求极其苛刻。我发现在很多情况下,内存的比例差异比CPU周期的比例差异要重要得多。
  11. Memory Graph. You also can jam up the engine making GC more expensive. Profilers do tend to show time spent in GC these days. It's not only a problem when it comes to allocating and freeing more. You'll also create a larger object graph to traverse and things like that so the GC consumes more cycles. If you create a million objects and then hardly touch them, depending on the engine it might turn out to have more of an ambient performance impact than you expected. I have proven that this does at least make the gc run for longer when objects are disposed of. That is there tends to be a correlation with memory used and the time it takes to GC. However there are cases where the time is the same regardless of the memory. This indicates that the graph makeup (layers of indirection, item count, etc) has more impact. That's not something that is always easy to predict.
  12. 内存图。您还可以阻塞引擎,使GC更贵。分析器确实倾向于显示在GC中花费的时间。这不仅仅是分配和释放更多资源的问题。您还将创建一个更大的要遍历的对象图和类似的东西,以便GC消耗更多的周期。如果您创建了一百万个对象,然后几乎不去碰它们,那么根据引擎的不同,可能会产生比您预期的更多的环境性能影响。我已经证明,这至少可以使gc在处理对象时运行更长时间。也就是说,与所使用的内存和花在GC上的时间有相关性。然而,在某些情况下,不管内存大小,时间都是相同的。这表明图形构成(间接层、项数等)具有更大的影响。这并不总是容易预测的。
  13. Not many people use chained prototypes extensively, myself included I have to admit. Prototype chains can be expensive in theory. Someone will but I've not measured the cost. If you instead build your objects entirely in the constructor and then have a chain of inheritance as each constructor calls a parent constructor upon itself, in theory method access should be much faster. On the other hand you can accomplish the equivalent if it matters (such as flatten the prototypes down the ancestor chain) and you don't mind breaking things like hasOwnProperty, perhaps instanceof, etc if you really need it. In either case things start to get complex once you down this road when it comes to performance hacks. You'll probably end up doing things you shouldn't be doing.
  14. 没有多少人广泛地使用链式原型,包括我自己在内,我不得不承认。理论上,原型链是昂贵的。有人会的,但我还没计算成本。如果您完全在构造函数中构建对象,然后在每个构造函数调用父构造函数时拥有一个继承链,那么理论上,方法访问应该要快得多。另一方面,如果它很重要(比如在祖先链上把原型压平),你也可以完成相应的任务,而且你不介意破坏像hasOwnProperty,或者instanceof之类的东西,如果你真的需要它的话。在这两种情况下,一旦遇到性能破坏,事情就会变得复杂起来。你可能会做一些不该做的事情。
  15. Many people don't directly use either approach you've presented. Instead they make their own things using anonymous objects allowing method sharing any which way (mixins for example). There are a number of frameworks as well that implement their own strategies for organising modules and objects. These are heavily convention based custom approaches. For most people and for you your first challenge should be organisation rather than performance. This is often complicated in that Javascript gives many ways of achieving things versus languages or platforms with more explicit OOP/namespace/module support. When it comes to performance I would say instead to avoid major pitfalls first and foremost.
  16. 许多人不会直接使用你所提出的任何一种方法。相反,它们使用匿名对象创建自己的东西,允许方法以任何方式共享(例如mixin)。也有许多框架实现了它们自己的组织模块和对象的策略。这些都是基于大量约定的自定义方法。对于大多数人和你来说,你的第一个挑战应该是组织而不是表现。这通常是很复杂的,因为Javascript提供了许多实现事物的方法,而不是具有更明确的OOP/名称空间/模块支持的语言或平台。当谈到性能时,我想说的是,首先要避免主要的缺陷。
  17. There's a new Symbol type that's supposed to work for private variables and methods. There are a number of ways to use this and it raises a host of questions related to performance and access. In my tests the performance of Symbols wasn't great compared to everything else but I never tested them thoroughly.
  18. 有一个新的符号类型,它应该适用于私有变量和方法。有许多方法可以使用它,并且它引发了许多与性能和访问相关的问题。在我的测试中,与其他东西相比,符号的性能并不是很好,但我从来没有对它们进行过彻底的测试。

Disclaimers:

免责声明:

  1. There are lots of discussions about performance and there isn't always a permanently correct answer for this as usage scenarios and engines change. Always profile but also always measure in more than one way as profiles aren't always accurate or reliable. Avoid significant effort into optimisation unless there's definitely a demonstrable problem.
  2. 有很多关于性能的讨论,并且随着使用场景和引擎的改变,并不总是有一个永久正确的答案。通常情况下,配置文件并不总是准确或可靠的,但也总是以多种方式测量。避免在优化方面做出重大努力,除非确实存在明显的问题。
  3. It's probably better instead to include performance checks for sensitive areas in automated testing and to run when browsers update.
  4. 最好在自动测试中包含对敏感区域的性能检查,并在浏览器更新时运行。
  5. Remember sometimes battery life matters as well as perceptible performance. The slowest solution might turn out faster after running an optimising compiler on it (IE, a compiler might have a better idea of when restricted scope variables are accessed than properties marked as private by convention). Consider backend such as node.js. This can require better latency and throughput than you would often find on the browser. Most people wont need to worry about these things with something like validation for a registration form but the number of diverse scenarios where such things might matter is growing.
  6. 记住,有时电池寿命和可感知性能一样重要。在上面运行优化编译器之后,最慢的解决方案可能会运行得更快(例如,编译器可能更清楚何时访问受限制的范围变量,而不是按照约定标记为私有的属性)。考虑后端,如node.js。这可能需要比浏览器更好的延迟和吞吐量。大多数人不需要担心诸如注册表的验证之类的事情,但是这些事情可能很重要的不同场景的数量正在增加。
  7. You have to be careful with memory allocation tracking tools in to persist the result. In some cases where I didn't return and persist the data it was optimised out entirely or the sample rate was not sufficient between instantiated/unreferenced, leaving me scratching my head as to how an array initialised and filled to a million registered as 3.4KiB in the allocation profile.
  8. 您必须小心使用内存分配跟踪工具来持久化结果。在某些情况下,当我没有返回并保存数据时,它完全被优化了,或者在实例化/未引用的情况下,样本速率是不够的,这让我对数组初始化和在分配配置文件中被填入的100万注册为3.4KiB感到困惑。
  9. In the real world in most cases the only way to really optimise an application is to write it in the first place so you can measure it. There are dozens to hundreds of factors that can come into play if not thousands in any given scenario. Engines also do things that can lead to asymmetric or non-linear performance characteristics. If you define functions in a constructor, they might be arrow functions or traditional, each behaves differently in certain situations and I have no idea about the other function types. Classes also don't behave the same in terms as performance for prototyped constructors that should be equivalent. You need to be really careful with benchmarks as well. Prototyped classes can have deferred initialisation in various ways, especially if your prototyped your properties as well (advice, don't). This means that you can understate initialisation cost and overstate access/property mutation cost. I have also seen indications of progressive optimisation. In these cases I have filled a large array with instances of objects that are identical and as the number of instances increase the objects appear to be incrementally optimised for memory up to a point where the remainder is the same. It is also possible that those optimisations can also impact CPU performance significantly. These things are heavily dependent not merely on the code you write but what happens in runtime such as number of objects, variance between objects, etc.
  10. 在现实世界中,在大多数情况下,真正优化应用程序的唯一方法是首先编写它,以便您可以度量它。在任何给定的场景中,都有数十到数百个因素可以发挥作用,如果不是数千个的话。引擎也会做一些会导致不对称或非线性性能的事情。如果在构造函数中定义函数,它们可能是箭头函数或传统函数,它们在某些情况下的行为都不同,我不知道其他函数类型。类的行为在性能上也不与应该等效的原型构造函数相同。对于基准,您也需要非常小心。原型类可以以各种方式进行延迟初始化,特别是如果您的属性也进行了原型化(建议不要这样做)。这意味着您可以低估初始化成本和超状态访问/属性突变成本。我也看到了渐进式优化的迹象。在这些情况下,我已经在一个大数组中填充了相同的对象实例,并且随着实例数量的增加,对象似乎会逐渐优化,以使内存达到与其他对象相同的点。这些优化还可能显著影响CPU性能。这些东西不仅严重依赖于您编写的代码,而且还依赖于运行时发生的事情,比如对象的数量、对象之间的差异等等。