【问题标题】:Benefits of using `Object.create` for inheritance使用 `Object.create` 进行继承的好处
【发布时间】:2023-03-22 13:24:01
【问题描述】:

我一直在尝试理解 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 = Object.create(Animal.prototype);

这也有效。

使用Object.create 有什么好处还是我错过了什么?

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

【问题讨论】:

  • 如果我不得不猜测,你会注意到在 Dog.prototype = new Animal(); 之后扩展 Animal 原型时的区别。 Dog 不会继承此扩展。但无法在这里测试该理论。
  • @SimonBoudrias 不幸的是,我在这些问题中没有找到任何有用的东西
  • 简短回答:运行Animal 构造函数可能会产生不良副作用。
  • 通过Dog.prototype = new Animal();,dog 的原型获得了Animal 实例的所有属性,还有name 属性。但是对于Object.create(Animal.prototype);,原型只能获得Animal原型的属性。
  • 仅作记录:这是实现类继承的另一种方式,取自 Backbone.js:var Surrogate = function(){ this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate;

标签: javascript


【解决方案1】:

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

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

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

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

继承建立is-a关系,例如DogisAnimal。这如何用构造函数和原型对象表示?

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

Dog.prototype = new Animal();

这是因为 Animal instance 继承自 Animal prototype 对象。 但是这也意味着每只狗都继承自一个特定的Animal实例。这似乎有点奇怪。实例特定代码不应该只在 constructor 函数中运行吗?突然,instance 特定代码和 prototype 方法似乎混在一起了。

此时我们实际上并不想运行Animal instance 特定代码,我们只需要来自Animal prototype 对象的所有方法。这就是Object.create 让我们做的事情:

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

这里我们不是创建一个新的Animal 实例,我们只得到原型方法。 instance 特定的代码在构造函数内部准确地执行:

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

最大的优势是Object.create始终工作。仅当构造函数不期望任何参数时,使用 new Animal() 才有效。想象一下,如果构造函数看起来像这样:

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

你总是必须将一个字符串传递给Animal,否则你会得到一个错误。当你做Dog.prototype = new Animal(??);时你会通过什么?实际上,你传递哪个字符串并不重要,只要传递 something,希望这能告诉你这是一个糟糕的设计。


有人说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。
这意味着使用 Object.create(SuperClass.prototype) 是 ES5 中“更正确”的方法。

【讨论】:

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

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

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

【讨论】:

    【解决方案3】:

    我想稍微说明一下区别:

    当你写 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 提到的那样不想要的结果。

    【讨论】:

      【解决方案4】:

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

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

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

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

      【讨论】:

      • 如果 Animal 构造函数有不希望的副作用,那是设计或代码的问题,而不是继承技术。
      猜你喜欢
      • 2023-03-20
      • 2016-02-15
      • 2015-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多