Javascript 为继承使用“Object.create”的好处

Javascript 为继承使用“Object.create”的好处,javascript,Javascript,我一直在尝试用ECMAScript 5中引入的新对象.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.proto

我一直在尝试用ECMAScript 5中引入的新
对象.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'); }
function Animal(name) { 
    this.name = name.toLowerCase();
}
class Dog extends Animal {

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

}
我只是给狗的原型分配了一个新创建的动物对象,一切都像一个符咒:

var dog1 = new Dog();
dog1.print(); // prints 'Dog'
dog1.bark(); // prints 'bark'
dog1.name; //prints 'Dog'
但是人们(没有解释)说
Dog.prototype=新动物()不是继承的工作方式,我应该使用Object.create方法:

Dog.prototype = Object.create(Animal.prototype);
这同样有效

使用
Object.create
有什么好处?还是我遗漏了什么


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

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

var Animal = function(name) {
    this.name = name;
    Animal.instances.push(this);
};
Animal.instances = [];
此版本将跟踪已创建的所有实例。你不想把你的
狗的原型记录在那里


其次,
Dog.prototype=Animal.prototype
是一个坏主意,因为这意味着
bark
将成为
Animal
的一种方法。在下面的例子中,我假设您只对
对象的原因感兴趣。创建
是设置继承的首选方法

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

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

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

  • 继承建立is-a关系,例如,
    动物
    。这是如何用构造函数和原型对象表示的

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

    Dog.prototype = new Animal();
    
    这是因为
    Animal
    实例继承自
    Animal
    prototype对象这也意味着每只狗都从一个特定的
    动物
    实例继承。这似乎有点奇怪。实例特定的代码不应该只在构造函数中运行吗?突然间,特定于实例的代码和原型方法似乎混合在一起了

    我们实际上不想在那一刻运行特定于实例的
    Animal
    代码,我们只需要
    Animal
    prototype对象中的所有方法。这就是
    对象。创建
    让我们做:

    Dog.prototype = Object.create(Animal.prototype);
    
    这里我们不是创建一个新的
    Animal
    实例,我们只得到原型方法。特定于实例的代码在构造函数内部的正确位置执行:

    function Dog() { 
       Animal.call(this, 'Dog'); 
    }
    
    最大的优点是
    Object.create
    始终有效。仅当构造函数不需要任何参数时,使用
    new Animal()
    才有效。想象一下,如果构造函数看起来像这样:

    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'); }
    
    function Animal(name) { 
        this.name = name.toLowerCase();
    }
    
    class Dog extends Animal {
    
      bark() {
        alert('bark');
      }
    
    }
    
    您必须始终将字符串传递给
    Animal
    ,否则将出现错误。当你做
    Dog.prototype=新动物(?)时,你会通过什么?实际上,传递哪个字符串并不重要,只要传递一些东西就行,希望这能向您表明这是一个糟糕的设计


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

    Animal.prototype
    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引入了一种新语法来创建构造函数和原型方法,如下所示:

    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'); }
    
    function Animal(name) { 
        this.name = name.toLowerCase();
    }
    
    class Dog extends Animal {
    
      bark() {
        alert('bark');
      }
    
    }
    
    这比我上面解释的更方便,但事实证明,
    extends
    还使用了一个内部等价物
    Object.create
    来设置继承。请参阅中的步骤2和3。

    这意味着使用
    Object.create(SuperClass.prototype)
    是ES5中“更正确”的方法。

    我试图稍微说明一下区别:

    下面是编写
    新动物()
    时的基本情况:

    下面是
    对象的基本情况。创建

        //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
    };
    
    第二种方法是:

    Dog.prototype = {
        name: undefined,
        __proto__: Animal.prototype
    };
    
    Dog.prototype = {
        __proto__: Animal.prototype
    };
    
    您实际上不需要在原型中包含
    name
    属性,因为您已经使用
    Animal.call将其分配给了
    Dog
    实例(此为“Dog”)


    您的主要目标是让您的
    实例访问
    动物
    原型的所有属性,这两种方法都可以实现。然而,第一种方法做了一些在您的情况下并不真正需要的额外工作,甚至可能导致Pumbaa80提到的不想要的结果。

    让我们仅用代码来理解它

    A。