前言
我们在Prototypes [上]中介绍了什么是Prototype也介绍了JavaScript中类似Class的机制,而本章将会介绍类似继承
的机制。
(Prototypal) Inheritance
我们在上一章中提介绍了一个範例
function Foo(name) {this.name = name;}Foo.prototype.myName = function() {return this.name;};var a = new Foo( "a" );a.myName(); // "a"
我们透过new关键字建立出Object a并将它的Prototype链接到Foo以获得myName这个method,这就是原型继承机制。
(图片来源 : You Don't Know JavaScript)
透过上面的图展现了a1到Foo.prptotype的委派和Bar.prototype到Foo.prototype的委派,某种程度上来说类似于Parent-Child继承的概念,但是这些箭头的方向代表着是链接而不是複製
,我们将此上图的链接转换为程式码并进行讲解。
function Foo(name){ this.name = name;};// 委派myName method到Foo.prototype上Foo.prototype.myName = function(){ return this.name;};function Bar(name, label){ Foo.call(this, name); this.label = label;};// link Bar.prototype to Foo.prototypeBar.prototype = Object.create( Foo.prototype );// 委派myLabel method到Bar.prototype上Bar.prototype.myLabel = function(){ return this.label;};const a = new Bar('a', 'Obj a');a.myName(); // aa.myLabel(); // Obj a
值得注意的是,上面程式码中Bar.prototype = Object.create( Foo.prototype );
的操作,它代表着建立一个新的Object并将该Object内部的Prototype链接到指定的对象(Foo.prototype),也可以说是建立一个新的Bar.prototype并将他链接到Foo.prototype(取代原本Bar.prototype)
。
这么做是因为,由于我们在建立Bar这个函数的时候JavaScript会自动帮我们在Bar中建议一个.prototype的属性,但他却没有链接到Foo.prototype,所以我们需要将原本的丢弃并建立一个新的让他链接到Foo.prototype上。
除了使用Object.create(...)
之外,也可以使用下面两种方法也可以使用但是可能并不会像我们预期的那样。
// donsn't work like you wantBar.prototype = Foo.prototype// works but with side-effectsBar.prototype = new Foo();
使用Bar.prototype = Foo.prototype
不会和我们预期的行为一样,这样Bar.prototype是对、Foo.prototype的Reference(浅拷贝),代表Bar和Foo都链接到同一个Foo.prototype Object上,这意味着如果在Bar.prototype上添加了一个method时也会同时修改到Foo.prototype。虽然使用Bar.prototype = new Foo();
也可以创建一个新的Object并且也可以链接到Foo.prototype上,但是如果使用Foo(...)构造函数执行这个操作的话会有副作用(Foo()会被调用产生预期外的行为)。所以我们使用Object.create(...)来创建一个新的物件并链接到Foo.prototype且没有调用Foo的副作用,但缺点是我们必须先建立一个新Object之后再将他的prototype给抛弃,不过在ES6中提供了一个method(Object.setPrototypeOf(...)
)可以让我们直接修改现有Object的prototype链接。
Object.setPrototypeOf( Bar.prototype, Foo.prototype );
Inspecting "Class" Relationships
如果在JavaScript中想找到一个Object他委託到哪一个Object上,我们该怎么做?在传统的OOP环境下这个行为称为introspection
或reflection
。
function Foo(){ // ...}Foo.prototype.blah = ...;const a = new Foo();
方法一:使用instanceof
a instanceof Foo; // true
它代表着a在整个[[Prototype]]链中,有没有出现在那个被Foo.prototype所指向的Object中?
,但是如果你有两个任意Object,你想调查这两个Object通过[[Prototype]]链相互关联,单靠instanceof是无法做到的。
方法二:使用[[Prototype]] reflection
Foo.prototype.isPrototypeOf( a ); // true
使用isPrototypeOf(...)
代表在a的整个[[Prototype]]中,Foo.prototype是否出现过?
,
我们也可以透过使用isPrototypeOf(...)
来调查两个任意Object的互相关联。
// b是否是否出现在c的[[Prototype]]链中?b.isPrototypeOf( c );
我们我们使用Object.getPrototypeOf(...)
来取得单一Object的[[Prototype]]。
Object.getPrototypeOf(a); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
而大多数浏览器中支援的一种非标準的方法也可以访问到Object的[[Prototype]]
a._proto_;// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
这个神奇的_proto_
属性跟constructor属性一样不存在于本身Object上,而是存在于内建的Object.prototype
,虽然它看起来像一个属性,但实际上将它看做是一个getter/setter会更合适。
结论
本篇章中介绍了Prototypal Inheritance的方法和JavaScript中的introspection方法,下面我们来做一些整理:
new关键字建立出Object它会链接到创建他的函数原型上以获得函数原型上的property/method,这便是原型继承的概念。参考文献
You Don't Know JavaScript