jQuery 使用 $.extend 和模块模式的简单 javascript 继承
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16659326/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Simple javascript inheritance using $.extend and module pattern
提问by Ron Gilchrist
I have wondered for a couple years now what people think of doing inheritance with a module-pattern-esque constructor pattern and WITHOUT normal prototypal inheritance. Why do programmers not use a module pattern for non-singleton js classes? For me the advantages are:
几年来,我一直想知道人们如何看待使用模块模式式构造函数模式进行继承而没有正常的原型继承。为什么程序员不对非单例 js 类使用模块模式?对我来说,优点是:
- Very clear public and private scope (easy to understand the code and the api)
- No needing to track the 'this' pointer via $.proxy(fn, this) in callbacks
- No more var that = this, etc. with event handlers, etc. Whenever I see a 'this', I know it is context that is being passed into a callback, it is NOT something I am tracking to know my object instance.
- 非常清晰的公共和私有范围(易于理解代码和 api)
- 无需在回调中通过 $.proxy(fn, this) 跟踪“this”指针
- 不再有 var that = this 等事件处理程序等。每当我看到“this”时,我知道它是传递给回调的上下文,这不是我要跟踪以了解我的对象实例的内容。
Disadvantages:
缺点:
- Small perf degradation
- Risk possible "wag of the finger" from Doug Crockford?
- 性能下降小
- 道格·克罗克福德 (Doug Crockford) 可能冒“摇手指”的风险?
Consider this (just run in any js console)
考虑一下(只需在任何 js 控制台中运行)
var Animal = function () {
var publicApi = {
Name: 'Generic',
IsAnimal: true,
AnimalHello: animalHello,
GetHelloCount:getHelloCount
};
var helloCount = 0;
function animalHello() {
helloCount++;
console.log(publicApi.Name + ' says hello (animalHello)');
}
function getHelloCount(callback) {
callback.call(helloCount);
}
return publicApi;
};
var Sheep = function (name) {
var publicApi = {
Name: name || 'Woolie',
IsSheep: true,
SheepHello: sheepHello
};
function sheepHello() {
publicApi.AnimalHello();
publicApi.GetHelloCount(function() {
console.log('i (' + publicApi.Name + ') have said hello ' + this + ' times (sheepHello anon callback)');
});
}
publicApi = $.extend(new Animal(), publicApi);
return publicApi;
};
var sheepie = new Sheep('Sheepie');
var lambie = new Sheep('Lambie');
sheepie.AnimalHello();
sheepie.SheepHello();
lambie.SheepHello();
My question is what are the drawbacks to this approach that I am not seeing? Is this a good approach?
我的问题是我没有看到这种方法的缺点是什么?这是一个好方法吗?
Thanks!
谢谢!
[update]
[更新]
Thanks for the great responses. Wish I could give everyone the bounty. It was what I was looking for. Basically what I thought. I would never use module pattern to construct more than a few instances of something. Usually only a couple. The reason I think it has its advantages is whatever small perf degradation you see is recaptured in the simplicity of the coding experience. We have a LOT of code to write these days. We also have to reuse other peoples' code and personally I appreciate when someone has taken the time to create a nice elegant pattern rather than dogmatically adhering to prototypal inheritance when it makes sense.
感谢您的精彩回复。希望我能给每个人赏金。这正是我要找的。基本上就是我想的。我永远不会使用模块模式来构造多个实例。通常只有一对。我认为它具有优势的原因是您看到的任何小的性能下降都在编码体验的简单性中被重新捕获。这些天我们有很多代码要编写。我们还必须重用其他人的代码,我个人很欣赏有人花时间创建一个漂亮的优雅模式,而不是在有意义的时候教条地坚持原型继承。
回答by AnthonyS
I think it boils down to the issue of performance. You mentioned that there is small performance degradation, but this really depends on scale of the application(2 sheep vs 1000 sheep). Prototypal inheritance should not be ignoredand we can create an effective module pattern using a mix of functional and prototypal inheritance.
我认为这归结为性能问题。您提到性能下降很小,但这实际上取决于应用程序的规模(2 只羊对 1000 只羊)。原型继承不应被忽视,我们可以使用功能和原型继承的混合来创建有效的模块模式。
As mentioned in the post JS - Why use Prototype?, one of the beauties of prototypeis that you only need to initialize the prototypal members only once, whereas members within the constructor are created for each instance. In fact, you can access prototype directly without creating a new object.
正如在JS - 为什么使用 Prototype 一文中提到的那样?,prototype的优点之一是你只需要初始化原型成员一次,而构造函数中的成员是为每个实例创建的。实际上,您可以直接访问原型,而无需创建新对象。
Array.prototype.reverse.call([1,2,3,4]);
//=> [4,3,2,1]
function add() {
//convert arguments into array
var arr = Array.prototype.slice.call(arguments),
sum = 0;
for(var i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
add(1,2,3,4,5);
//=> 15
In your functions, there is extra overhead to create a completely new Animal and sheep each time a constructor is invoked. Some members such as Animal.name are created with each instance, but we know that Animal.name is static so it would be better to instantiate it once. Since your code implies that Animal.name should be the same across all animals, it is easy to update Animal.name for all instance simply by updating Animal.prototype.name if we moved it to the prototype.
在您的函数中,每次调用构造函数时都会产生额外的开销来创建一个全新的 Animal 和绵羊。一些成员如 Animal.name 是随每个实例一起创建的,但我们知道 Animal.name 是静态的,因此最好将其实例化一次。由于您的代码暗示 Animal.name 应该在所有动物中都相同,因此如果我们将它移动到原型,只需更新 Animal.prototype.name 就可以轻松更新所有实例的 Animal.name 。
Consider this
考虑这个
var animals = [];
for(var i = 0; i < 1000; i++) {
animals.push(new Animal());
}
Functional inheritance/Module Pattern
功能继承/模块模式
function Animal() {
return {
name : 'Generic',
updateName : function(name) {
this.name = name;
}
}
}
//update all animal names which should be the same
for(var i = 0;i < animals.length; i++) {
animals[i].updateName('NewName'); //1000 invocations !
}
vs. Prototype
与原型
Animal.prototype = {
name: 'Generic',
updateName : function(name) {
this.name = name
};
//update all animal names which should be the same
Animal.prototype.updateName('NewName'); //executed only once :)
As shown above with your currently module pattern we lose effeciency in updating properties that should be in common to all members.
如上所示,使用您当前的模块模式,我们失去了更新所有成员共有的属性的效率。
If you are concered about visibility, I would use the same modular method you are currently using to encapsulate private members but also use priviledged membersfor accessing these members should they need to be reached. Priviledged membersare public members that provide an interface to access private variables. Finally add common members to the prototype.
如果您关心可见性,我将使用您当前使用的相同模块化方法来封装私有成员,但也使用 特权成员来访问需要访问的这些成员。特权成员是提供访问私有变量的接口的公共成员。最后向原型添加公共成员。
Of course going this route, you will need to keep track of this. It is true that in your implementation there is
当然要走这条路,你需要跟踪这一点。确实,在您的实施中有
- No needing to track the 'this' pointer via $.proxy(fn, this) in callbacks
- No more var that = this, etc. with event handlers, etc. Whenever I see a 'this', I know it is context that is being passed into a callback, it is NOT something I am tracking to know my object instance.
- 无需在回调中通过 $.proxy(fn, this) 跟踪“this”指针
- 不再有 var that = this 等事件处理程序等。每当我看到“this”时,我知道它是传递给回调的上下文,这不是我要跟踪以了解我的对象实例的内容。
, but you are are creating a very large object each time which will consume more memoryin comparison to using some prototypal inheritance.
,但是您每次都在创建一个非常大的对象,与使用某些原型继承相比,它会消耗更多内存。
Event Delegation as Analogy
事件委托作为类比
An analogyto gaining performance by using prototypes is improved performance by using event delegationwhen manipulating the DOM.Event Delegation in Javascript
使用原型获得性能的一个类比是在操作 DOM 时使用事件委托来提高性能。Javascript 中的事件委托
Lets say you have a large grocery list.Yum.
假设你有一个很大的购物清单。嗯。
<ul ="grocery-list">
<li>Broccoli</li>
<li>Milk</li>
<li>Cheese</li>
<li>Oreos</li>
<li>Carrots</li>
<li>Beef</li>
<li>Chicken</li>
<li>Ice Cream</li>
<li>Pizza</li>
<li>Apple Pie</li>
</ul>
Let's say that you want to log the item you click on. One implementation would be to attach an event handler to every item(bad), but if our list is very long there will be a lot of events to manage.
假设您想记录您单击的项目。一种实现是为每个 item(bad) 附加一个事件处理程序,但如果我们的列表很长,就会有很多事件需要管理。
var list = document.getElementById('grocery-list'),
groceries = list.getElementsByTagName('LI');
//bad esp. when there are too many list elements
for(var i = 0; i < groceries.length; i++) {
groceries[i].onclick = function() {
console.log(this.innerHTML);
}
}
Another implementation would be to attach one event handler to the parent(good)and have that one parent handle all the clicks. As you can see this is similar to using a prototype for common functionality and significantly improves performance
另一种实现是将一个事件处理程序附加到父级(良好)并让该父级处理所有点击。如您所见,这类似于使用原型来实现通用功能并显着提高性能
//one event handler to manage child elements
list.onclick = function(e) {
var target = e.target || e.srcElement;
if(target.tagName = 'LI') {
console.log(target.innerHTML);
}
}
Rewrite using Combination of Functional/Prototypal Inheritance
使用功能/原型继承的组合重写
I think the combination of functional/prototypal inheritance can be written in an easy understandable manner. I have rewritten your code using the techniques described above.
我认为功能/原型继承的组合可以用一种易于理解的方式编写。我已经使用上述技术重写了您的代码。
var Animal = function () {
var helloCount = 0;
var self = this;
//priviledge methods
this.AnimalHello = function() {
helloCount++;
console.log(self.Name + ' says hello (animalHello)');
};
this.GetHelloCount = function (callback) {
callback.call(null, helloCount);
}
};
Animal.prototype = {
Name: 'Generic',
IsAnimal: true
};
var Sheep = function (name) {
var sheep = new Animal();
//use parasitic inheritance to extend sheep
//http://www.crockford.com/javascript/inheritance.html
sheep.Name = name || 'Woolie'
sheep.SheepHello = function() {
this.AnimalHello();
var self = this;
this.GetHelloCount(function(count) {
console.log('i (' + self.Name + ') have said hello ' + count + ' times (sheepHello anon callback)');
});
}
return sheep;
};
Sheep.prototype = new Animal();
Sheep.prototype.isSheep = true;
var sheepie = new Sheep('Sheepie');
var lambie = new Sheep('Lambie');
sheepie.AnimalHello();
sheepie.SheepHello();
lambie.SheepHello();
Conclusion
结论
The takeaway is to use both prototypal and functional inheritance to their advantages both to tackle performance and visibility issues. Lastly, if you are working on a small JavaScript applications and these performance issues are not a concern, then your method would be viable approach.
要点是同时使用原型继承和函数继承来解决性能和可见性问题。最后,如果您正在开发一个小型 JavaScript 应用程序并且这些性能问题不是问题,那么您的方法将是可行的方法。
回答by Bergi
a module-pattern-esque constructor pattern
一个模块模式式的构造器模式
This is known as parasitic inheritanceor functional inheritance.
这被称为寄生继承或功能继承。
For me the advantages are:
- Very clear public and private scope (easy to understand this code and the api)
对我来说,优点是:
- 非常清晰的公共和私有范围(易于理解此代码和 api)
The same holds true for the classical constructor pattern. Btw, in your current code it's not super clear whether animalHello
and getHelloCount
are supposed to be private or not. Defining them right in the exported object literal might be better if you care about that.
这同样适用于经典的构造器模式。顺便说一句,在您当前的代码中,是否animalHello
以及getHelloCount
是否应该是私有的还不是很清楚。如果您关心的话,在导出的对象文字中正确定义它们可能会更好。
- No needing to track the 'this' pointer via $.proxy(fn, this) in callbacks
- No more var that = this, etc. with event handlers, etc. Whenever I see a 'this', I know it is context that is being passed into a callback, it is NOT something I am tracking to know my object instance.
- 无需在回调中通过 $.proxy(fn, this) 跟踪“this”指针
- 不再有 var that = this 等事件处理程序等。每当我看到“this”时,我知道它是传递给回调的上下文,这不是我要跟踪以了解我的对象实例的内容。
That's basically the same. You either use a that
dereference orbinding to solve this problem. And I don't see this as a huge disadvantage, since the situation where you'd use object "methods" directly as callbacks are pretty rare - and apart from the context you often want to feed additional arguments. Btw, you're using a that
reference as well in your code, it's called publicApi
there.
这基本上是一样的。您可以使用that
取消引用或绑定来解决此问题。而且我不认为这是一个巨大的缺点,因为您直接使用对象“方法”作为回调的情况非常罕见 - 除了上下文之外,您经常想要提供额外的参数。顺便说一句,您that
在代码中也使用了引用,它在publicApi
那里被调用。
Why do programmers not use a module pattern for non-singleton js classes?
为什么程序员不对非单例 js 类使用模块模式?
Now, you've named some disadvantages yourself already. Additionally, you are loosing prototypical inheritance - with all its advantages (simplicity, dynamism, instanceof
, …). Of course, there are cases where they don't apply, and your factory function is perfectly fine. It is indeed used in these cases.
现在,您已经自己指出了一些缺点。此外,您正在失去原型继承 - 及其所有优点(简单性、动态性instanceof
、……)。当然,有些情况下它们不适用,并且您的工厂功能非常好。它确实用于这些情况。
publicApi = $.extend(new Animal(), publicApi); … … new Sheep('Sheepie');
publicApi = $.extend(new Animal(), publicApi); … … new Sheep('Sheepie');
These parts of your code are a little confusing as well. You are overwriting the variable with a different object here, and it happens in the middle-to-end of your code. It would be better to see this as a "declaration" (you're inheriting the parent properties here!) and put it at the top of your function - right as var publicApi = $.extend(Animal(), {…});
代码的这些部分也有点令人困惑。您在这里用不同的对象覆盖变量,它发生在代码的中端。最好将其视为“声明”(您在这里继承父属性!)并将其放在函数的顶部 - 就像var publicApi = $.extend(Animal(), {…});
Also, you should not use the new
keyword here. You do not use the functions as constructors, and you don't want to create instances that inherit from Animal.prototype
(which slows down your execution). Also, it confuses people that might expect prototypical inheritance from that constructor invocation with new
. For clarity, you even might rename the functions to makeAnimal
and makeSheep
.
此外,您不应在new
此处使用关键字。您不将函数用作构造函数,并且您不想创建继承自的实例Animal.prototype
(这会减慢您的执行速度)。此外,它使那些可能期望从构造函数调用中获得原型继承的人与new
. 为清楚起见,您甚至可以将函数重命名为makeAnimal
和makeSheep
。
What would be a comparable approach to this in nodejs?
在 nodejs 中,与此类似的方法是什么?
This design pattern is completely environment-independent. It will work in Node.js just as like as at the client and in every other EcmaScript implementation. Some aspects of it even are language-independent.
这种设计模式完全独立于环境。它将在 Node.js 中工作,就像在客户端和所有其他 EcmaScript 实现中一样。它的某些方面甚至与语言无关。
回答by Parthik Gosar
With your approach, you wont be able to override functions and call the super function so conveniently.
使用您的方法,您将无法如此方便地覆盖函数和调用超级函数。
function foo ()
{
}
foo.prototype.GetValue = function ()
{
return 1;
}
function Bar ()
{
}
Bar.prototype = new foo();
Bar.prototype.GetValue = function ()
{
return 2 + foo.prototype.GetValue.apply(this, arguments);
}
Also, in the prototype approach, you can share data among all the instances of the object.
此外,在原型方法中,您可以在对象的所有实例之间共享数据。
function foo ()
{
}
//shared data object is shared among all instance of foo.
foo.prototype.sharedData = {
}
var a = new foo();
var b = new foo();
console.log(a.sharedData === b.sharedData); //returns true
a.sharedData.value = 1;
console.log(b.sharedData.value); //returns 1
One more advantage of the prototype approach would be to save memory.
原型方法的另一个优点是节省内存。
function foo ()
{
}
foo.prototype.GetValue = function ()
{
return 1;
}
var a = new foo();
var b = new foo();
console.log(a.GetValue === b.GetValue); //returns true
Whereas in of your approach,
而在你的方法中,
var a = new Animal();
var b = new Animal();
console.log(a.AnimalHello === b.AnimalHello) //returns false
This means with each new object, a new instance of the functions are created where as it is shared among all objects incase of prototype approach. This wont make much difference incase of few instances but when large number of instances are created, it would show a considerable difference.
这意味着对于每个新对象,都会创建一个新的函数实例,因为它在原型方法的情况下在所有对象之间共享。这在少数实例的情况下不会有太大区别,但是当创建大量实例时,它会显示出相当大的差异。
Also, one more powerful feature of prototype would be once all objects are created, you can still change the properties among all objects at once (only if they are not altered after object creation).
此外,原型的一个更强大的功能是,一旦创建了所有对象,您仍然可以立即更改所有对象之间的属性(仅当它们在对象创建后未更改时)。
function foo ()
{
}
foo.prototype.name = "world";
var a = new foo ();
var b = new foo ();
var c = new foo();
c.name = "bar";
foo.prototype.name = "hello";
console.log(a.name); //returns 'hello'
console.log(b.name); //returns 'hello'
console.log(c.name); //returns 'bar' since has been altered after object creation
Conclusion:If the above mentioned advantages of the prototype approach are not so useful for your application, your approach would be better.
结论:如果原型方法的上述优点对您的应用程序不是那么有用,那么您的方法会更好。