我已经阅读了一些关于javascript原型继承模式的教程,但我不确定哪个是以下两个中的最佳实践。我注意到很多人都使用这种继承模式:
var A = function (){}
A.prototype = {}
var B = function () {
A.apply(this, arguments); // Calling the constructor of A
}
B.prototype = new A(); // Inherit from A through an instance
替代方案,有些来源改为采用以下模式:
var A = function (){}
A.prototype = {}
var B = function () {
A.apply(this, arguments); // Calling the constructor of A
}
for (var prop in A.prototype) {
B.prototype[prop] = A.prototype[prop]; // Inherit from A by copying every property/methods from A
}
虽然两种模式都有效,但我很少看到人们使用后一种继承模式(即从父模型中复制每个属性/方法) - 为什么?将属性/方法直接从父级复制到子级是否有问题?另外,这两种模式在某些方面本质上是不同的吗?
谢谢。
答案 0 :(得分:9)
这些模式非常不同,正如您可能已经猜到的那样,第一种模式更好(但不是最好的模式)。让我们比较一下:
B.prototype = Object.create(A.prototype);
这使用Object.create
函数将B.prototype
设置为新内容[[Prototype]]
为A.prototype
的新对象。这基本上就是您想要的:它会使B
个实例在适当时委托给A.prototype
。
B.prototype = new A();
在ES5的Object.create
出现之前,这就是过去的工作方式。 (虽然there were workarounds,即使它们没有被广泛使用。)这种方法的问题是任何仅实例数据属性也最终都在B.prototype
,这是不希望的。此外,调用A
的构造函数的任何副作用都将发生。从本质上讲,这种方法混淆了两个相关但不同的概念:对象实例化和对象构建。
for (var prop in A.prototype) {
B.prototype[prop] = A.prototype[prop];
}
这种模式有几个问题:
A
原型链中的任何位置的属性一直复制到Object
,直接复制到B.prototype
。这违背了原型继承的大部分目的,其中你应该委托原型链,而不是将它压缩到一个单一的层次。A.prototype
及其原型链中获取可枚举的属性,因为for ... in
会跳过不可枚举的属性。A.prototype
(或其原型链)上定义的任何getter或setter,只是复制它们的值而不是getter / setter函数。答案 1 :(得分:2)
这是使用工厂模式的另一种方法,没有原型:
/* parent */
function Animal(name, legs)
{
/* members and methods declared within a new object */
var obj =
{
name: name,
legs: legs,
description: function ()
{
return this.name + " is an animal";
},
printNumLegs: function ()
{
return this.name + " has " + this.legs + " legs";
}
}
return obj;
}
/* subclass */
function Cat()
{
/* apply parent arguments in the context of the current object */
var obj = Animal.apply(this, arguments);
/* public member */
obj.furColor = "black";
/* private member */
var sleeping = false;
/* private method */
function printSleepingState()
{
/* can access public member ('name') without it being passed as constructor argument */
return obj.name + " is " + (sleeping ? "sleeping" : "not sleeping");
}
/* declare a new method */
obj.printFurColor = function ()
{
return obj.name + " has " + obj.furColor + " fur";
}
/* overloading */
/* save parent method if needed */
var oldDescription = obj.description;
obj.description = function ()
{
return oldDescription.apply(obj) + " and " + printSleepingState();
}
return obj;
}
/* create object without new */
var kitty = Cat("Kitty", 4);
kitty.furColor = "yellow";
无论如何都没有“最佳”方法......这都是品味问题。
答案 2 :(得分:1)
第一个变体看起来像一个普通的原型继承。它有一个小的减号,如果函数A需要一些必需的参数,你应该在为函数B设置原型时传递它们。它还为原型创建一个父对象的实例。如果您不需要这样的行为,则第二个变体看起来很有用。
顺便说一下: 看看The third variant to make prototypal inheritance。这有时非常有用。
编辑:据我所知,第三种变体是Domenic解决方案的老式变体(对于没有Object.create功能的旧浏览器)。
答案 3 :(得分:0)
我意识到这个答案已经很晚了,但我想我会为未来的读者添加评论。
我想补充一点,除了上面提到的选项还有其他选择。
继承的替代方法是混合。访问http://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/
混合和方法#2之间有一些相似之处,但它们是不同的。 在许多情况下,Mixins可以比上面描述的继承技术灵活得多,因为它们允许一个对象从几个混合中混合(接受行为)。 尽管mix-ins添加了行为,但它们通常不会向对象添加状态。