[JS] You Don't Know JavaScript [this & Object Prototypes] - Prototypes [下]

前言

我们在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,这就是原型继承机制。

https://ithelp.ithome.com.tw/upload/images/20201217/20124767YjfAL1WY8B.png
(图片来源 : 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.prototype
Bar.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(); // a
a.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 want
Bar.prototype = Foo.prototype

// works but with side-effects
Bar.prototype = new Foo();
  1. 使用Bar.prototype = Foo.prototype不会和我们预期的行为一样,这样Bar.prototype是对、Foo.prototype的Reference(浅拷贝),代表Bar和Foo都链接到同一个Foo.prototype Object上,这意味着如果在Bar.prototype上添加了一个method时也会同时修改到Foo.prototype。
  2. 虽然使用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环境下这个行为称为introspectionreflection

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


<<:  Day50. 范例:十二生肖

>>:  Week40 - 各种安全性演算法的应用 - 窜改、抵赖实作 [高智能方程序系列]

【PHP Telegram Bot】Day06 - 安装 PHP 与设定环境变数

今天要来安装执行 PHP 程序的程序,PHP 的解释器(直译器)。 通常 PHP 都是跑在网站服务...

[想试试看JavaScript ] 各种事件处理

事件种类 浏览器的事件有非常多种,这篇介绍一些比较常见的事件 事件处理,是指程序不会马上执行,直到触...

Chapter1-DJ最爱的音频动感图像(IV)让音乐动起来!开篇基础设定和动画框架

话不多说先上图 从左到右依序执行,最後该函式会再呼叫自己一次,图中淡化的区块是下个章节的主题 然後把...

Day 4 - Object 物件组合技

前言 前两天讲完了一些常用的 Array 组合技,今天来介绍一下它的青梅竹马(?) - Object...

2020 东京奥运 — 新科技、黑科技大汇集,你也许没看过

上礼拜 2020 东京奥运结束闭幕式,结束四年(?)一次的奥运会, 恭喜台湾创下佳绩,荣获 2 金 ...