接下来,我假设您只对为什么使用Object.create
设置继承感兴趣。
为了理解其好处,首先要澄清JavaScript中“类”的由来。它由两个部分组成:
构造函数。此函数包含创建“类”实例的所有逻辑,即具体的实例代码。
原型对象。这是实例继承自的对象,包括所有应在所有实例之间共享的方法(和其他属性)。
继承建立了一个is-a关系,例如,一个Dog
是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.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 引入了一种新语法来创建构造函数和原型方法,如下所示:
class Dog extends Animal {
bark() {
alert('bark');
}
}
这种方法比我上面解释的更方便,但事实证明,extends
也使用了一个内部等价于Object.create
来设置继承。请参见ES6 draft中的步骤2和3。
这意味着在ES5中使用Object.create(SuperClass.prototype)
是“更正确”的方法。
Animal
构造器可能会产生不希望的副作用。 - user123444555621Dog.prototype = new Animal();
,狗的原型获得了动物实例的所有属性,包括name
属性。但是通过Object.create(Animal.prototype);
,原型只获得了Animal
的原型的属性。 - basilikumvar Surrogate = function(){ this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate;
- user123444555621