使用`Object.create`进行继承的好处

57

我一直试图理解ECMAScript 5中引入的新方法Object.create

通常,当我想使用继承时,我会像这样做:

var Animal = function(name) { this.name = name; }
Animal.prototype.print = function() { console.log(this.name); }

var Dog = function() 
{ 
  return Animal.call(this, 'Dog'); 
}

Dog.prototype = new Animal();
Dog.prototype.bark = function() { console.log('bark'); }

我只是将一个新创建的Animal对象分配给了Dog的原型,一切都像魔术般地运行:

var dog1 = new Dog();
dog1.print(); // prints 'Dog'
dog1.bark(); // prints 'bark'
dog1.name; //prints 'Dog'

但是人们(不加解释)说Dog.prototype = new Animal();不是继承的正确方式,我应该使用Object.create方法:

但人们说Dog.prototype = new Animal()不符合继承的工作原理,建议使用Object.create()方法代替:

Dog.prototype = Object.create(Animal.prototype);

使用 Object.create 有什么好处呢?或者我理解有误吗?

更新:有人说 Dog.prototype = Animal.prototype; 也可以,现在我完全困惑了。


3
简短回答:运行Animal构造器可能会产生不希望的副作用。 - user123444555621
@KirillIvlev 可能吧 - Sumurai8
1
通过 Dog.prototype = new Animal();,狗的原型获得了动物实例的所有属性,包括 name 属性。但是通过 Object.create(Animal.prototype);,原型只获得了 Animal 的原型的属性。 - basilikum
@KirillIvlev 请看下面的答案作为示例。 - user123444555621
1
只是为了记录:这里有另一种实现类继承的方法,来自于 Backbone.js:var Surrogate = function(){ this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; - user123444555621
显示剩余6条评论
4个回答

126

接下来,我假设您只对为什么使用Object.create设置继承感兴趣。

为了理解其好处,首先要澄清JavaScript中“类”的由来。它由两个部分组成:

  1. 构造函数。此函数包含创建“类”实例的所有逻辑,即具体的实例代码。

  2. 原型对象。这是实例继承自的对象,包括所有应在所有实例之间共享的方法(和其他属性)。

继承建立了一个is-a关系,例如,一个DogAnimal。如何以构造函数和原型对象的术语表达这一点?

显然,狗必须具有与动物相同的方法,即Dog原型对象必须以某种方式合并来自Animal原型对象的方法。有多种方法可以实现这一点。您经常会看到这样的写法:

Dog.prototype = new Animal();

之所以能这样做,是因为Animal实例继承自Animal原型对象。 但是这也意味着每只狗都继承自一个特定的Animal实例。这似乎有些奇怪。难道实例特定的代码不应该只在构造函数中运行吗?突然间,实例特定的代码和原型方法似乎混合在了一起。

实际上,我们并不想在那个时刻运行Animal实例特定的代码,我们只想要来自Animal原型对象的所有方法。这就是Object.create让我们所能做到的:

Dog.prototype = Object.create(Animal.prototype);

在这里,我们并不会创建一个新的Animal实例,我们只会获取原型方法。特定于实例的代码会恰好在构造函数内部执行:

function Dog() { 
   Animal.call(this, 'Dog'); 
}

最大的好处是,Object.create 总是有效的。只有当构造函数不需要任何参数时,使用 new Animal() 才有效。想象一下如果构造函数长这样:

function Animal(name) { 
    this.name = name.toLowerCase();
}

在调用Animal时,您必须始终传递一个字符串,否则会出现错误。当您执行Dog.prototype = new Animal(??);时,您将传递什么?实际上不重要,只要传递任何东西即可。这表明这是一个糟糕的设计。


有人说 Dog.prototype = Animal.prototype; 也可以工作。现在我完全困惑了

所有从Animal.prototypeDog.prototype添加属性的方法都“可以工作”。但是,这些解决方案的质量不同。在这种情况下,您将面临一个问题,即您向Dog.prototype添加的任何方法也将被添加到Animal.prototype中。

例如:

Dog.prototype.bark = function() {
    alert('bark');
};

由于 Dog.prototype === Animal.prototype,现在所有的 Animal 实例都有了一个名为 bark 的方法,这显然不是你想要的。

Object.create(甚至是 new Animal)通过创建一个继承自 Animal.prototype 的新对象并将其作为 Dog.prototype,在继承中添加了一层间接性。


ES6 中的继承

ES6 引入了一种新语法来创建构造函数和原型方法,如下所示:

class Dog extends Animal {

  bark() {
    alert('bark');
  }

}

这种方法比我上面解释的更方便,但事实证明,extends也使用了一个内部等价于Object.create来设置继承。请参见ES6 draft中的步骤2和3。
这意味着在ES5中使用Object.create(SuperClass.prototype)是“更正确”的方法。


14
+1 表示“只有在构造函数不需要任何参数的情况下才能正常工作”。 - user123444555621
2
好的解释 - 做得很好。但是,如果Animal真的需要在构造函数中设置某些东西,那么允许Object.create创建一个半成品的Animal是否是一个优点还是一个缺点,这是可以争论的。个人认为这是一个缺点。我仍然不相信Object.create()是如此神奇。 - user949300
通常我只是将一个选项变量传递给我的构造函数,然后进行扩展。那么如果我想要使用选项来创建 new Animal(),它看起来会像这样:new Animal({ species: 'dog', sound: 'bark' })但是,如果你不使用 jQuery,你需要创建自己的扩展函数。 - Banning
作为新的JavaScript开发人员,我们是否应该继续深入学习ES6中使用Object.Create创建和共享对象原型?特别是如果我们想要真正遵循对象行为委托的原则。Felix,你有什么看法? - klewis
Object.create(Animal.prototype) 的想法是什么?它创建另一个具有 Animal.prototype 的新对象并返回新的对象实例,它是如何工作的? - SameerShaik
@SameerShaik:Dog.prototype需要分配一个对象,该对象从Animal.prototype继承,并且不是Animal.prototype本身。 Object.create(...)可以实现这一点。 - Felix Kling

9

首先,运行 Animal 构造函数可能会产生不良的副作用。请看下面这个例子:

var Animal = function(name) {
    this.name = name;
    Animal.instances.push(this);
};
Animal.instances = [];

这个版本会跟踪所有已创建的实例。你不想让Dog.prototype被记录在那里。

其次,Dog.prototype = Animal.prototype 是一个坏主意,因为这意味着bark将变成Animal的一个方法。


如果Animal构造函数具有不良副作用,则这是设计或代码的问题,而不是继承技术的问题。 - user949300

8

我将尝试简单说明一下它们的区别:

当你编写new Animal()时,基本上发生的情况是:

    //creating a new object
    var res = {};

    //setting the internal [[prototype]] property to the prototype of Animal
    if (typeof Animal.prototype === "object" && Animal.prototype !== null) {
        res.__proto__ = Animal.prototype;
    }

    //calling Animal with the new created object as this
    var ret = Animal.apply(res, arguments);

    //returning the result of the Animal call if it is an object
    if (typeof ret === "object" && ret !== null) {
        return ret;
    }

    //otherise return the new created object
    return res;

以下是 Object.create 的基本操作:

    //creating a new object
    var res = {};

    //setting the internal [[prototype]] property to the prototype of Animal
    if (typeof Animal.prototype !== "object") {
        throw "....";
    }
    res.__proto__ = Animal.prototype;

    //return the new created object
    return res;

所以它做的事情是相同的,但它不调用Animal函数,并且总是返回新创建的对象。 在您的情况下,您最终得到两个不同的对象。使用第一种方法,您将获得:

Dog.prototype = {
    name: undefined,
    __proto__: Animal.prototype
};

第二种方法可以得到:

Dog.prototype = {
    __proto__: Animal.prototype
};

您不需要在原型中拥有name属性,因为您已经使用Animal.call(this, 'Dog');将其分配给了您的Dog实例。
您的主要目标是让您的Dog实例访问Animal原型的所有属性,这两种方法都可以实现。然而,第一种方法在您的情况下做了一些不必要的额外操作,或者甚至可能导致不需要的结果,正如Pumbaa80所提到的那样。

7

让我们只用代码来理解它;

A.prototype = B.prototype;

function B() {console.log("I am B");this.b1= 30;}
    B.prototype.b2 = 40;

    function A() {console.log("I am A");this.a1= 10;}
    A.prototype.a2 = 20;

    A.prototype = B.prototype;

    A.prototype.constructor = A; 

    var a = new A;
    var b = new B;

    console.log(a);//A {a1: 10, b2: 40}
    console.log(b);//B {b1: 30, b2: 40}

    console.log(A.prototype.constructor);//A
    console.log(B.prototype.constructor);//A
    console.log(A.prototype);//A {b2: 40}
    console.log(B.prototype);//A {b2: 40}
    console.log(a.constructor === A); //true
    console.log(b.constructor === A); //true

console.log(a.a2);//undefined

enter image description here

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

function B() {console.log("I am B");this.b1= 30;}
B.prototype.b2 = 40;

function A() {console.log("I am A");this.a1= 10;}
A.prototype.a2 = 20;

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

A.prototype.constructor = A; 

var a = new A;
var b = new B;

console.log(a);//A {a1: 10, constructor: function, b2: 40}
console.log(b);//B {b1: 30, b2: 40} 

console.log(A.prototype.constructor);//A
console.log(B.prototype.constructor);//B
console.log(A.prototype);//A {constructor: function, b2: 40}
console.log(B.prototype);//B {b2: 40}
console.log(a.constructor === A); //true
console.log(b.constructor === B); //true
console.log(a.a2);//undefined

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接