使用Object.createinheritance的好处
我一直在围绕在ECMAScript 5中引入的新的Object.create
方法。
通常当我想使用inheritance,我做这样的事情:
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'); }
我只是将一个新创build的动物物体分配给狗的原型,一切都像一个魅力:
var dog1 = new Dog(); dog1.print(); // prints 'Dog' dog1.bark(); // prints 'bark' dog1.name; //prints 'Dog'
但是人们(没有解释)在说Dog.prototype = new Animal();
不是inheritance的工作方式,我应该使用Object.create方法:
Dog.prototype = Object.create(Animal.prototype);
这也有效。
使用Object.create
什么好处,或者我错过了什么?
更新:有人说, Dog.prototype = Animal.prototype;
也可以工作。 所以现在我完全困惑
在下面,我假设你只关心为什么Object.create
更适合于设置inheritance。
要理解这些好处,首先要明确一下JavaScript中的“类”是什么。 你有两个部分:
-
构造函数 这个函数包含所有创build“类”实例的逻辑,即实例特定的代码。
-
原型对象。 这是实例inheritance的对象。 它包含所有应该在所有实例之间共享的方法(和其他属性)。
inheritancebuild立了一个关系,例如,一只Dog
是一只Animal
。 这是如何用构造函数和原型对象表示的?
显然,狗必须和动物有相同的方法,那就是Dog
原型对象必须以某种方式包含Animal
原型对象的方法。 有多种方法可以做到这一点。 你会经常看到这个:
Dog.prototype = new Animal();
这是可行的,因为Animal
实例inheritance了Animal
原型对象。 但这也意味着每只狗都从一个特定的Animal
实例inheritance。 这似乎有点奇怪。 不应该在构造函数中只运行实例特定的代码? 突然出现的实例特定的代码和原型方法似乎是混合的。
我们实际上并不想运行Animal
实例特定的代码,我们只需要Animal
原型对象的所有方法。 这就是Object.create
让我们做的事情:
Dog.prototype = Object.create(Animal.prototype);
在这里我们不是创build一个新的Animal
实例,我们只获取原型方法。 特定于实例的代码在构造函数内部正确执行:
function Dog() { Animal.call(this, 'Dog'); }
最大的优点是Object.create
将始终工作。 使用new Animal()
只适用于构造函数不期望任何参数。 想象一下,如果构造函数看起来像这样:
function Animal(name) { this.name = name.toLowerCase(); }
你总是要传递一个string给Animal
,否则你会得到一个错误。 当你做Dog.prototype = new Animal(??);
? 实际上,只要传递一些东西 ,希望告诉你这是糟糕的devise,那么你传递哪一个string并不重要。
有人说,
Dog.prototype = Animal.prototype;
也可以工作。 所以现在我完全困惑
从Animal.prototype
到Dog.prototype
的属性“添加”的所有内容都将“起作用”。 但是解决scheme的质量不同。 在这种情况下,您将遇到以下问题:您添加到Dog.prototype
任何方法也将被添加到Animal.prototype
。
例:
Dog.prototype.bark = function() { alert('bark'); };
由于Dog.prototype === Animal.prototype
,现在所有的Animal
实例都有一个方法叫,这当然不是你想要的。
Object.create
(甚至是new Animal
)通过创build一个inheritance自Animal.prototype
的新对象,并且新对象变成Dog.prototype
,为inheritance添加一个间接级别。
ES6中的inheritance
ES6引入了一个新的语法来创build构造函数和原型方法,如下所示:
class Dog extends Animal { bark() { alert('bark'); } }
这比我上面解释的更方便,但是事实certificate, extends
还使用了一个与Object.create
相当的内部来设置inheritance。 请参阅ES6草案中的步骤2和3。
这意味着使用Object.create(SuperClass.prototype)
是ES5中“更正确”的方法。
首先,运行Animal
构造函数可能会产生不希望的副作用。 考虑这个:
var Animal = function(name) { this.name = name; Animal.instances.push(this); }; Animal.instances = [];
该版本将跟踪所有已创build的实例。 你不希望你的Dog.prototype
被logging在那里。
其次, Dog.prototype = Animal.prototype
是一个坏主意,因为那意味着bark
会成为Animal
一种方法。
我试图说明一下这个区别:
这是基本上当你写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
函数,它也总是返回新创build的对象。 在你的情况下,你最终有两个不同的对象。 用第一种方法你得到:
Dog.prototype = { name: undefined, __proto__: Animal.prototype };
用第二种方法得到:
Dog.prototype = { __proto__: Animal.prototype };
你真的不需要在你的原型中有name
属性,因为你已经用Animal.call(this, 'Dog');
把它分配给你的Dog
实例Animal.call(this, 'Dog');
。
您的主要目标是让您的Dog
实例访问Animal
原型的所有属性,这是通过两种方法实现的。 然而,第一种方法会做一些额外的事情,在您的情况下并不是真正需要的,或者甚至可能会导致Pumbaa80提到的不需要的结果。
让我们只用代码来理解它;
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