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

IT技术 javascript
2021-01-15 22:54:00

我一直试图围绕Object.createECMAScript 5 中引入的新方法。

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

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 = Object.create(Animal.prototype);

这也有效。

使用有什么好处Object.create或者我错过了什么?

更新:有人说这Dog.prototype = Animal.prototype;也可以。所以现在我完全糊涂了

4个回答

在下文中,我假设您只对为什么Object.create设置继承更可取感兴趣

为了理解这些好处,让我们首先澄清一下 JavaScript 中的“类”是由什么组成的。你有两个部分:

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

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

继承建立了is-a关系,例如 a Dog is an Animal这在构造函数和原型对象方面是如何表达的?

显然,狗必须有相同的方法作为一种动物,那就是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.prototypeto “添加”属性的所有内容都Dog.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 草案中的第 2 步和第 3 步
这意味着 usingObject.create(SuperClass.prototype)是 ES5 中“更正确”的方法。

+1 表示“仅在构造函数不需要任何参数时才有效”
2021-03-12 22:54:00
我通常只是将和选项变量传递给我的构造函数,然后得到扩展。然后,如果我想new Animal()但有选项,它看起来像 new Animal({ species: 'dog', sound: 'bark' }) 这样,但是,如果您不使用 jQuery,则需要创建自己的扩展函数。
2021-03-16 22:54:00
对于我们新的 JavaScript 开发人员来说,继续深入研究在 ES6 中使用 Object.Create 创建和共享对象原型是否是一个好习惯?特别是如果我们想忠实于对象的行为委托?菲利克斯你有什么看法?
2021-03-24 22:54:00
Object.create(Animal.prototype) 的想法是什么?使用 Animal.prototype 创建另一个新对象并返回新对象实例它是如何工作的?
2021-04-02 22:54:00
很好的解释 - 干得好。但。如果 Animal 真的需要在构造函数中设置一些东西,人们可能会争论允许 Object.create 创建一个半生不熟的 Animal 是有利还是不利。个人觉得是减分项。我仍然不相信 Object.create() 是如此美妙。
2021-04-05 22:54:00

首先,运行Animal构造函数可能会产生不希望的副作用。考虑一下:

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

此版本将跟踪已创建的所有实例。你不希望你Dog.prototype被记录在那里。

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

如果那个 Animal 构造函数有不想要的副作用,那是设计或代码的问题,而不是继承技术的问题。
2021-04-03 22:54:00

我试图说明一点差异:

以下是您编写时的基本情况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在原型中拥有该属性,因为您已经Dog使用Animal.call(this, 'Dog');.

您的主要目标是让您的Dog实例访问Animal原型的所有属性,这可以通过两种方法实现。但是,第一种方法会执行一些在您的情况下并不真正需要的额外内容,或者甚至会像 Pumbaa80 提到的那样导致不需要的结果。

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

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

在此处输入图片说明

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

在此处输入图片说明