我们已经看过了许多JavaScript程序中常用的模拟类行为的方法,但是如果没有“继承”机制的话,JavaScript中的类就只是一个空架子。
实际上,我们已经了解了通常被称作原型继承的机制,a可以“继承”Foo.prototype并访问Foo.prototype的myName()函数。但是之前我们只把继承看作是类和类之间的关系,并没有把它看作是类和实例之间的关系:
这张图不仅展示出对象(实例)a1到Foo.prototype的委托关系,还展示出Bar.prototype到Foo.prototype的委托关系,而后者和类继承很相似,只有箭头的方向不同。图中由下到上的箭头表明这是委托关联,不是复制操作。
这段代码的核心部分就是语句Bar.prototype=Object.create(Foo.prototype)。调用Object.create(..)会凭空创建一个“新”对象并把新对象内部的[[Prototype]]关联到你指定的对象(本例中是Foo.prototype)。
换句话说,这条语句的意思是:“创建一个新的Bar.prototype对象并把它关联到Foo.prototype”。
声明functionBar(){..}时,和其他函数一样,Bar会有一个.prototype关联到默认的对象,但是这个对象并不是我们想要的Foo.prototype。因此我们创建了一个新对象并把它关联到我们希望的对象上,直接把原始的关联对象抛弃掉。
注意,下面这两种方式是常见的错误做法,实际上它们都存在一些问题:
//和你想要的机制不一样!
Bar.prototype=Foo.prototype;
//基本上满足你的需求,但是可能会产生一些副作用:(
Bar.prototype=newFoo();
Bar.prototype=Foo.prototype并不会创建一个关联到Bar.prototype的新对象,它只是让Bar.prototype直接引用Foo.prototype对象。因此当你执行类似Bar.prototype.myLabel=...的赋值语句时会直接修改Foo.prototype对象本身。显然这不是你想要的结果,否则你根本不需要Bar对象,直接使用Foo就可以了,这样代码也会更简单一些。
Bar.prototype=newFoo()的确会创建一个关联到Bar.prototype的新对象。但是它使用了Foo(..)的“构造函数调用”,如果函数Foo有一些副作用(比如写日志、修改状态、注册到其他对象、给this添加数据属性,等等)的话,就会影响到Bar()的“后代”,后果不堪设想。
因此,要创建一个合适的关联对象,我们必须使用Object.create(..)而不是使用具有副作用的Foo(..)。这样做唯一的缺点就是需要创建一个新对象然后把旧对象抛弃掉,不能直接修改已有的默认对象。
如果能有一个标准并且可靠的方法来修改对象的[[Prototype]]关联就好了。在ES6之前,我们只能通过设置.__proto__属性来实现,但是这个方法并不是标准并且无法兼容所有浏览器。ES6添加了辅助函数Object.setPrototypeOf(..),可以用标准并且可靠的方法来修改关联。
我们来对比一下两种把Bar.prototype关联到Foo.prototype的方法:
//ES6之前需要抛弃默认的
Bar.prototypeBar.ptototype=Object.create(Foo.prototype);
//ES6开始可以直接修改现有的
Bar.prototypeObject.setPrototypeOf(Bar.prototype,Foo.prototype);
如果忽略掉Object.create(..)方法带来的轻微性能损失(抛弃的对象需要进行垃圾回收),它实际上比ES6及其之后的方法更短而且可读性更高。不过无论如何,这是两种完全不同的语法。