javascript继承模式比较

时间:2012-04-09 06:45:21

标签: javascript prototypal-inheritance

我已经阅读了一些关于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
}

虽然两种模式都有效,但我很少看到人们使用后一种继承模式(即从父模型中复制每个属性/方法) - 为什么?将属性/方法直接从父级复制到子级是否有问题?另外,这两种模式在某些方面本质上是不同的吗?

谢谢。

4 个答案:

答案 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添加了行为,但它们通常不会向对象添加状态。