我一直试图围绕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;
也可以。所以现在我完全糊涂了
答案 0 :(得分:112)
在下文中,我假设您只对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
,为继承添加一级间接。 / p>
ES6中的继承
ES6引入了一种新语法来创建构造函数和原型方法,如下所示:
class Dog extends Animal {
bark() {
alert('bark');
}
}
这比我上面解释的更方便,但事实证明,extends
还使用内部等效于Object.create
来设置继承。请参阅ES6 draft中的步骤2和3
这意味着在ES5中使用Object.create(SuperClass.prototype)
是“更正确”的方法。
答案 1 :(得分:9)
首先,运行Animal
构造函数可能会产生不良副作用。考虑一下:
var Animal = function(name) {
this.name = name;
Animal.instances.push(this);
};
Animal.instances = [];
此版本将跟踪已创建的所有实例。您不希望在那里录制Dog.prototype
。
其次,Dog.prototype = Animal.prototype
是一个坏主意,因为这意味着bark
将成为Animal
的方法。
答案 2 :(得分:7)
我试图稍微说明一下这个区别:
这是写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提到的不必要的结果。
答案 3 :(得分:5)
让我们只用代码来理解它;
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