在下文中,我假设您只对为什么Object.create
设置继承更可取感兴趣。
为了理解这些好处,让我们首先澄清一下 JavaScript 中的“类”是由什么组成的。你有两个部分:
该构造函数。该函数包含创建“类”实例的所有逻辑,即实例特定代码。
该原型对象。这是实例继承的对象。它包含应该在所有实例之间共享的所有方法(和其他属性)。
继承建立了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.prototype
to “添加”属性的所有内容都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 中“更正确”的方法。