4

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

4 回答 4

9

这些模式非常不同,您可能已经猜到,第一个更好(但不是最好的)。让我们比较一下:

最现代、最好的模式

B.prototype = Object.create(A.prototype);

这使用该Object.create函数设置B.prototype为一个新对象,其内部[[Prototype]]A.prototype. 这基本上正是您想要的:它会使实例在适当的时候B委托给它。A.prototype

原始模式

B.prototype = new A();

在 ES5 出现之前,这就是过去的做法Object.create。(尽管有一些变通方法,即使它们没有被广泛使用。)这种方法的问题是任何仅实例的数据属性也最终会出现在 上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 函数。
  • 对于任何试图阅读您的代码的人来说,这看起来很奇怪。
于 2012-04-09T07:25:01.187 回答
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";

无论如何,没有“最好”的方法……这完全是口味问题。

于 2012-04-09T08:34:18.480 回答
1

第一个变体看起来像一个常见的原型继承。它有一个小缺点,如果函数 A 需要一些必需的参数,则应在为函数 B 设置原型时传递它们。它还为原型创建父对象的实例。如果您不需要这种行为,则第二个变体看起来很有用。

顺便说一句:看一下The third variant to make prototypal inheritance。这有时也非常有用。

编辑:据我了解,第三个变体是 Domenic 解决方案的老式变体(适用于没有 Object.create 功能的旧浏览器)。

于 2012-04-09T07:08:18.087 回答
0

我意识到这个答案已经晚了,但我想我会为未来的读者添加评论。

我想补充一点,除了上述之外,还有其他选择。

继承的替代方法是混入。访问http://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/

mix-ins 和方法 #2 之间有一些相似之处,但它们是不同的。在许多情况下,Mixins 可以比上面描述的继承技术灵活得多,因为它们允许一个对象从多个 mix-in 中混合(采取行为)。尽管 mix-ins 添加了行为,但它们通常不会向您的对象添加状态。

于 2014-01-16T08:47:44.220 回答