## 26 原型鏈和類
> 原文: [http://exploringjs.com/impatient-js/ch_proto-chains-classes.html](http://exploringjs.com/impatient-js/ch_proto-chains-classes.html)
>
> 貢獻者:[lq920320](https://github.com/lq920320)
在本書中,JavaScript 的面向對象編程(OOP)風格分四步介紹。本章包括步驟 2-4,[前一章](/docs/31.md)涵蓋步驟 1。全部步驟為(圖 [9](#fig:oop_steps2) )所示:
1. 單個對象:*對象*(JavaScript 的基本 OOP 構建塊)如何獨立工作?
2. **原型鏈:** 每個對象都有一個零個或多個 *原型對象鏈*。原型是 JavaScript 的核心繼承機制。
3. **類:** JavaScript 的 *類* 是對象的工廠。類及其實例之間的關系基于原型繼承。
4. **子類化:** *子類* 與其 *父類* 之間的關系也是基于原型繼承。

圖 9:此書將會用四個步驟介紹 JavaScript 中的面向對象編程
### 26.1 原型鏈
原型是 JavaScript 唯一的繼承機制:每個對象都有一個原型,它是 `null` 或一個對象。在后一種情況下,對象繼承了所有原型的屬性。
在對象字面值中,您可以通過特殊屬性 `__proto__` 設置原型:
```js
const proto = {
protoProp: 'a',
};
const obj = {
__proto__: proto,
objProp: 'b',
};
// obj 繼承 .protoProp:
assert.equal(obj.protoProp, 'a');
assert.equal('protoProp' in obj, true);
```
鑒于原型對象本身可以擁有原型,我們得到了一系列對象——即所謂*原型鏈*。這意味著繼承給我們的感覺是我們正在處理單個對象,但實際上我們處理的是對象鏈。
圖 [10](#fig:oo_proto_chain) 展示了`obj`的原型鏈是什么樣的。

圖 10: 對象鏈以 `obj` 為始,繼而后續的 `proto` 以及其他對象
非繼承屬性稱為*自身屬性*。 `obj`有一個自身屬性`.objProp`。
#### 26.1.1 JavaScript的操作:所有屬性 vs. 自身屬性
有的操作會涉及到所有屬性(自身和繼承的)。例如,獲取屬性:
```js
> const obj = { foo: 1 };
> typeof obj.foo // 自身屬性
'number'
> typeof obj.toString // 繼承屬性
'function'
```
另外一些操作則只會涉及其自身屬性。例如,`Object.key()`:
```js
> Object.keys(obj)
[ 'foo' ]
```
繼續閱讀另一個僅考慮自身屬性的操作:設置屬性。
#### 26.1.2 陷阱:只有原型鏈的第一個成員發生了變異
可能反直覺的原型鏈的一個方面是,可以通過對象設置*任何*屬性——即使是繼承的——僅改變該對象——而從不是原型中的某一屬性。
看一下如下對象 `obj`:
```js
const proto = {
protoProp: 'a',
};
const obj = {
__proto__: proto,
objProp: 'b',
};
```
在下一個代碼片段中,我們設置了繼承屬性 `obj.protoProp` (行A)。我們通過創建一個自身屬性來“改變”它:當讀取 `obj.protoProp` 時,首先要找到自身屬性,然后,它的值將覆蓋繼承屬性的值。
```js
// 最開始,obj 有一個自身屬性
assert.deepEqual(Object.keys(obj), ['objProp']);
obj.protoProp = 'x'; // (A)
// 我們創建了一個新的自身屬性:
assert.deepEqual(Object.keys(obj), ['objProp', 'protoProp']);
// 繼承屬性自身沒有改變:
assert.equal(proto.protoProp, 'a');
// 自身屬性覆蓋繼承屬性:
assert.equal(obj.protoProp, 'x');
```
`obj`的原型鏈如圖 11 所示。 [11](#fig:oo_overriding) 。

圖 11:`obj`的自身屬性 `.protoProp` 覆蓋了從 `proto` 繼承來的屬性。
#### 26.1.3 使用原型的提示(高級)
##### 26.1.3.1 避免`__proto__`(除了對象字面值)
我建議避免使用偽屬性 `__proto__`:正如我們稍后將看到的,并非所有的對象都有這一屬性。
但是,對象字面值中的 `__proto__` 是不同的,其中,它是一個內置函數,始終可用。
獲取和設置原型的推薦方法是:
- 獲取原型的最佳方法是通過以下方法:
```js
Object.getPrototypeOf(obj: Object) : Object
```
- 設置原型的最佳方法是創建對象——通過對象字面值中的__proto__或通過:
```js
Object.create(proto: Object) : Object
```
如果必須,你可以使用 `Object.setPrototypeOf()` 來更改現有對象的原型。但這可能會對性能產生負面影響。
以下是這些功能的使用方法:
```js
const proto1 = {};
const proto2 = {};
const obj = Object.create(proto1);
assert.equal(Object.getPrototypeOf(obj), proto1);
Object.setPrototypeOf(obj, proto2);
assert.equal(Object.getPrototypeOf(obj), proto2);
```
##### 26.1.3.2 檢查:對象是另一個的原型嗎?
到目前為止,“p是o的原型”總是意味著“p是o的直接原型”。 但它也可以更松散地使用,并且意味著p在o的原型鏈中。 可以通過以下方式檢查較弱的關系:
```js
p.isPrototypeOf(o)
```
例如:
```js
const a = {};
const b = {__proto__: a};
const c = {__proto__: b};
assert.equal(a.isPrototypeOf(b), true);
assert.equal(a.isPrototypeOf(c), true);
assert.equal(a.isPrototypeOf(a), false);
assert.equal(c.isPrototypeOf(a), false);
```
#### 26.1.4 通過原型共享數據
請參看以下代碼:
```js
const jane = {
name: 'Jane',
describe() {
return 'Person named ' + this.name;
},
};
const tarzan = {
name: 'Tarzan',
describe() {
return 'Person named ' + this.name;
},
};
assert.equal(jane.describe(), 'Person named Jane');
assert.equal(tarzan.describe(), 'Person named Tarzan');
```
我們有兩個非常相似的對象。兩者都有兩個屬性,其名稱為`.name`和`.describe`。另外,方法`.describe()`是相同的。我們怎樣才能避免重復該方法?
我們可以將它移動到一個對象 `PersonProto`,并使該對象成為`jane`和`tarzan`的(共享)原型:
```js
const PersonProto = {
describe() {
return 'Person named ' + this.name;
},
};
const jane = {
__proto__: PersonProto,
name: 'Jane',
};
const tarzan = {
__proto__: PersonProto,
name: 'Tarzan',
};
```
原型的名稱反映出`jane`和`tarzan`都是人類。

圖 12: 對象 `jane` 以及 `tarzan` 通過它們相同的原型 `PersonProto` 共享 `.describe()` 方法。
圖中的圖表 [12](#fig:oo_person_shared) 說明了三個對象是如何連接的:底部的對象現在包含特定于 `jane` 和 `tarzan` 的屬性。頂部的對象包含它們之間共享的屬性。
當你調用方法 `jane.describe()` 時,`this` 指向該方法調用的接收者,`jane` (在圖的左下角)。這就是該方法仍然有效的原因。調用 `tarzan.describe()` 同理。
```js
assert.equal(jane.describe(), 'Person named Jane');
assert.equal(tarzan.describe(), 'Person named Tarzan');
```
### 26.2 類
我們現在準備接受類,這基本上是用于設置原型鏈的緊湊語法。`JavaScript` 內部,類是不常提及的,而且你在使用的時候也很少看到的東西。對于其他面向對象編程語言的人來說,它們應該會感到熟悉一些。
#### 26.2.1 一個人類的類(class)
我們之前使用過 `jane` 和 `tarzan`,代表人類的單個對象。讓我們用 `類聲明` 歷史實現 `Person` 對象的工廠:
```js
class Person {
constructor(name) {
this.name = name;
}
describe() {
return 'Person named ' + this.name;
}
}
```
現在可以通過 `new Person()` 創建 `jane` 和 `tarzan`:
```js
const jane = new Person('Jane');
assert.equal(jane.name, 'Jane');
assert.equal(jane.describe(), 'Person named Jane');
const tarzan = new Person('Tarzan');
assert.equal(tarzan.name, 'Tarzan');
assert.equal(tarzan.describe(), 'Person named Tarzan');
```
#### 26.2.1.1 類表達式
有兩種類定義(定義類的方法):
- 類聲明:在前面的章節我們已經見過。
- 類表達式:接下來我們會看到。
類表達式可以是匿名的并可以進行命名:
```js
// 匿名類表達式
const Person = class { ··· };
// 命名類表達式
const Person = class MyClass { ··· };
```
命名類表達式與[命名函數表達式](/docs/28.md#2322普通功能的名稱)的命名方式類似。
以上便是初見 類,我們很快就會探索更多功能,但首先我們需要學習類的內部。
#### 26.2.2 內部的類(高級)
在類的內部有很多事情要做。讓我們看一下 `jane` 的圖表(圖 [13](#fig:oo_person_class) )。

圖 13:類 `Person` 具有屬性 `.prototype`,它指向一個對象,該對象是 `Person` 的所有實例的原型。 `jane` 就是這樣一個例子。
類`Person`的主要目的是在右側設置原型鏈(`jane`,然后是`Person.prototype`)。有趣的是,`Person` 類(`.constructor` 和 `.descript()`)內的兩個構造函數都是為 `Person.prototype` 創建了屬性,而不是為 `Person`。
這種稍微奇怪的方法的原因是向后兼容性:在類之前,*構造函數*([普通函數](/docs/28.md#2323普通功能扮演的角色),通過 `new` 運算符調用)通常用作對象的工廠。類通常是構造函數的更好語法,因此與舊代碼保持兼容。這解釋了為什么類是函數:
```js
> typeof Person
'function'
```
在本書中,我可以互換地使用術語 *構造函數(函數)* 和 *類*。
`.__proto__` 和 `.prototype` 很容易混淆。希望圖 [13](#fig:oo_person_class) 清楚說明了它們的區別:
- `.__proto__` 是用于訪問對象原型的偽屬性。
- `.prototype` 是一個普通的屬性,取決于 `new` 操作符的使用方式,它只是特殊的。這個命名并不理想:`Person.prototype` 沒有指向 `Person` 的原型,它指向 `Person` 的所有實例的原型。
##### 26.2.2.1 `Person.prototype.constructor`(高級)
圖 13 中有一個細節我們還沒有注意到:`Person.prototype.constructor` 指回`Person`:
```js
> Person.prototype.constructor === Person
true
```
由于向后兼容性,此設置也存在。但它也有兩個額外的好處。
首先,類的每個實例都繼承屬性`.constructor`。因此,給定一個實例,你可以通過它創建“類似”對象:
```js
const jane = new Person('Jane');
const cheeta = new jane.constructor('Cheeta');
// cheeta 也是 Person 的一個實例
// (instanceof 操作符稍后解釋)
assert.equal(cheeta instanceof Person, true);
```
其次,您可以獲取創建給定實例的類的名稱:
```js
const tarzan = new Person('Tarzan');
assert.equal(tarzan.constructor.name, 'Person');
```
#### 26.2.3 類定義:原型屬性
以下類聲明的主體中的所有構造函數都創建了 `Foo.prototype` 的屬性。
```js
class Foo {
constructor(prop) {
this.prop = prop;
}
protoMethod() {
return 'protoMethod';
}
get protoGetter() {
return 'protoGetter';
}
}
```
讓我們逐個看一下:
- 在創建 `Foo` 的新實例后調用 `.constructor()` 來設置該實例。
- `.protoMethod()` 是一種常規方法。它存儲在`Foo.prototype`中。
- `.protoGetter` 是存儲在 `Foo.prototype` 中的 getter (獲取方法)。
以下交互使用類 `Foo`:
```js
> const foo = new Foo(123);
> foo.prop
123
> foo.protoMethod()
'protoMethod'
> foo.protoGetter
'protoGetter'
```
#### 26.2.4 類定義:靜態屬性
一下類聲明的主體中所有構造函數都創建了所謂的*靜態屬性*—— `Bar` 的自身屬性:
```js
class Bar {
static staticMethod() {
return 'staticMethod';
}
static get staticGetter() {
return 'staticGetter';
}
}
```
靜態方法和靜態 get 方法使用如下:
```js
> Bar.staticMethod()
'staticMethod'
> Bar.staticGetter
'staticGetter'
```
#### 26.2.5 `instanceof`運算符
`instanceof` 運算符告訴你某個值是否是給定類的實例:
```js
> new Person('Jane') instanceof Person
true
> ({}) instanceof Person
false
> ({}) instanceof Object
true
> [] instanceof Array
true
```
在我們查看子類化之后,我們將在后面更詳細地探索 `instanceof` 運算符。
#### 26.2.6 為什么我推薦類
我推薦使用類,原因如下:
- 類是對象創建和繼承的通用標準,現在跨框架(React,Angular,Ember 等)廣泛支持。這是對以前事物的改進,幾乎每個框架都有自己的繼承庫。
- 他們幫助 IDE 和類型檢查器等工具完成工作并啟用新功能。
- 如果你是從其他語言來轉向JavaScript,并熟悉類,那么你可更快地開始學習。
- JavaScript 引擎會進行優化。也就是說,使用類的代碼幾乎比使用比使用自定義繼承庫的代碼更快。
- 你可以子類化內置構造函數,例如 `Error`。
這并不意味著類是完美的:
- 存在過度繼承的風險。
- 存在在類中放置太多函數細節的風險(當其中某些細節還是放在函數中時更好一些)。
- 他們在表面上和內部的運行過程是完全不同的。 換句話說,語法和語義之間存在脫節。 兩個例子是:
- 一個定義在類 `C` 中的內部方法,在對象 `C.prototype` 中創建了一個方法。
- 類即函數。
> **練習:實現一個類**
>
> `exercises/proto-chains-classes/point_class_test.js`
### 26.3 類的私有數據
本節描述了從外部隱藏對象的一些數據的技術。我們在類的上下文中討論它們,但它們也適用于通過對象字面值等直接創建的對象。
#### 26.3.1 私有數據:命名約定
第一種技術通過在其名稱前加下劃線來使屬性成為私有屬性。這不會以任何方式保護屬性;它只是向外界發出信號:“你不需要知道這個屬性。”
在以下代碼中,屬性 `._counter` 和 `._action` 是私有的。
```js
class Countdown {
constructor(counter, action) {
this._counter = counter;
this._action = action;
}
dec() {
this._counter--;
if (this._counter === 0) {
this._action();
}
}
}
// 這兩個屬性并非真正私有:
assert.deepEqual(
Object.keys(new Countdown()),
['_counter', '_action']);
```
使用這種方法,不會得到任何保護,私有命名可能會發生沖突。從好的方面來說,它簡單易用。
#### 26.3.2 私人數據:WeakMaps
另一種技術是使用 WeakMaps。在[關于 WeakMaps](/docs/38.md#3132通過-weakmaps-保存私人數據) 的章節中解釋了究竟是如何工作的。這里只是預覽:
```js
const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}
// 這兩個偽屬性是真正私有的:
assert.deepEqual(
Object.keys(new Countdown()),
[]);
```
這種技術為你提供了相當大的外部訪問保護,并且不會有任何命名沖突。但使用起來也更復雜。
#### 26.3.3 更多私有數據技術
本書介紹了類中私有數據的最重要技術。JavaScript 可能很快就會內置對私有數據的支持。有關詳細信息,請參閱 ECMAScript 提案[“類公共實例字段 & 私有實例字段“](https://github.com/tc39/proposal-class-fields)
[“探索ES6”](https://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes)中介紹了一些其他技術。
### 26.4 子類
類也可以子類化(“擴展”)現有類。例如,以下類 `Person` 的子類 `Employee`:
```js
class Person {
constructor(name) {
this.name = name;
}
describe() {
return `Person named ${this.name}`;
}
static logNames(persons) {
for (const person of persons) {
console.log(person.name);
}
}
}
class Employee extends Person {
constructor(name, title) {
super(name);
this.title = title;
}
describe() {
return super.describe() +
` (${this.title})`;
}
}
const jane = new Employee('Jane', 'CTO');
assert.equal(
jane.describe(),
'Person named Jane (CTO)');
```
注解兩條:
- 在 `.constructor()` 方法中,必須先通過 `super()` 調用父類構造函數,然后才能訪問 `this`。那是因為在調用父類構造函數之前 `this` 不存在(這種現象特定于類)。
- 靜態方法也是繼承的。例如,`Employee` 繼承靜態方法 `.logNames()`:
```js
> 'logNames' in Employee
true
```
>  **練習:子類化**
>
> `exercises/proto-chains-classes/color_point_class_test.js`
#### 26.4.1 內部的子類(高級)

圖 14: 這些是構成類 `Person` 它的子類 `Employee` 的對象。左邊是關于類的,右邊是關于 `Employee` 實例 `jane` 及其原型鏈。
上一節中的`Person`和`Employee`類由幾個對象組成(圖 [14](#fig:oo_subclassing) )。理解這些對象如何相關的一個關鍵見解是,有兩個原型鏈:
- 實例原型鏈,在右側。
- 類原型鏈,在左邊。
##### 26.4.1.1 實例原型鏈(右欄)
實例原型鏈以 `jane` 開始,接著是 `Employee.prototype` 和 `Person.prototype`。原則上,雖然原型鏈在此時結束,但我們還得到一個對象:`Object.prototype`。這個原型為幾乎所有對象提供服務,這也是為什么它包含在這里:
```js
> Object.getPrototypeOf(Person.prototype) === Object.prototype
true
```
##### 26.4.1.2 類原型鏈(左欄)
在類原型鏈中,`Employee` 首先出現,接下來是 `Person`。之后,鏈只接著連至 `Function.prototype`,因為`Person`是一個函數,函數需要`Function.prototype`的服務。
```js
> Object.getPrototypeOf(Person) === Function.prototype
true
```
#### 26.4.2 `instanceof`的詳細內容(高級)
我們還沒有看到`instanceof`如何真正起作用。給定表達式:
```js
x instanceof C
```
`instanceof`如何確定`x`是否是`C`的實例?它通過檢查`C.prototype`是否在`x`的原型鏈中來實現。也就是說,以下兩個表達式是等效的:
```js
x instanceof C
C.prototype.isPrototypeOf(x)
```
如果我們回到圖。 [14](#fig:oo_subclassing) ,我們可以確認原型鏈確實引導我們得到以下答案:
```js
> jane instanceof Employee
true
> jane instanceof Person
true
> jane instanceof Object
true
```
#### 26.4.3 內置對象的原型鏈(高級)
接下來,我們將使用我們的子類化知識來理解一些內置對象的原型鏈。以下工具功能 `p()` 幫助我們進行探索。
```js
const p = Object.getPrototypeOf.bind(Object);
```
我們提取 `Object` 的方法 `.getPrototypeOf()` 并將其分配給 `p`。
##### 26.4.3.1 `{}`的原型鏈
讓我們從檢查普通對象開始:
```js
> p({}) === Object.prototype
true
> p(p({})) === null
true
```

圖 15: 通過對象值創建的對象的原型鏈由該對象開始,接著是 `Object.prototype`,最后以 `null` 結束
圖 [15](#fig:proto_chain_object) 顯示了該原型鏈的圖表。我們可以看到`{}`確實是`Object`的實例 - `Object.prototype`在其原型鏈中。
##### 26.4.3.2 `[]`的原型鏈
Array 的原型鏈是什么樣的?
```js
> p([]) === Array.prototype
true
> p(p([])) === Object.prototype
true
> p(p(p([]))) === null
true
```

圖 16: 一個數組的原型鏈有幾個部分組成: 數組實例, `Array.prototype`, `Object.prototype`, `null`。
這個原型鏈(在圖 [16](#fig:proto_chain_array) 中可視化)告訴我們一個 Array 對象是`Array`的一個實例,它是`Object`的子類。
##### 26.4.3.3 `function () {}`的原型鏈
最后,普通函數的原型鏈告訴我們所有函數都是對象:
```js
> p(function () {}) === Function.prototype
true
> p(p(function () {})) === Object.prototype
true
```
##### 26.4.3.4 不是`Object`實例的對象
如果 `Object.prototype` 在其原型鏈中,則對象只是 `Object` 的實例。通過各種字面值創建的大多數對象是 `Object` 的實例:
```js
> ({}) instanceof Object
true
> (() => {}) instanceof Object
true
> /abc/ug instanceof Object
true
```
沒有原型的對象不是 `Object` 的實例:
```js
> ({ __proto__: null }) instanceof Object
false
```
`Object.prototype` 結束了大多數原型鏈。它的原型是`null`,這意味著它也不是 `Object` 的實例:
```js
> Object.prototype instanceof Object
false
```
##### 26.4.3.5 偽屬性.__ proto__究竟是如何工作的?
偽屬性.__ proto__由類Object通過getter和setter實現。可以像這樣實現:
```js
class Object {
get __proto__() {
return Object.getPrototypeOf(this);
}
set __proto__(other) {
Object.setPrototypeOf(this, other);
}
// ···
}
```
這意味著您可以通過在其原型鏈中創建一個沒有`Object.prototype`的對象來關閉 `.__ proto__`(參見上一節):
```js
> '__proto__' in {}
true
> '__proto__' in { __proto__: null }
false
```
#### 26.4.4 調度調用 vs 直接方法調用(高級)
讓我們來看一下方法調用如何與類一起工作。我們從再次訪問之前的 `jane` :
```js
class Person {
constructor(name) {
this.name = name;
}
describe() {
return 'Person named ' + this.name;
}
}
const jane = new Person('Jane');
```
圖 [17](#fig:jane_proto_chain) 有一個帶有`jane`原型鏈的圖表。

圖 17: `jane` 的原型鏈由 `jane` 開始接著是 `Person.prototype`。
正常方法調用是*調度式的*,`jane.describe()` 這一方法調用發生在兩步:
- 調度:在 `jane` 的原型鏈中,找到其鍵為'describe'的第一個屬性并檢索其值。
```js
const func = jane.describe;
```
- 調用:調用找到的值,同時將`this`設置為`jane`。
```js
func.call(jane);
```
這種動態查找方法的方式稱為*動態調度*。
你可以在繞過這種調度進行相同方法的直接調用:
```js
Person.prototype.describe.call(jane)
```
這次,我們通過`Person.prototype.describe`直接指向了方法,且沒有在原型鏈中進行搜索。我們還通過 `.call()` 以不同的方式進行了指定。
注意 `this` 總是指向原型鏈的開頭。這使得 `.describe()` 能夠訪問 `.name`。
##### 26.4.4.1 借用方法
使用 `Object.prototype` 的方法時,直接方法調用很有用。例如,`Object.prototype.hasOwnProperty(k)` 檢查 `this` 對象是否具有其鍵為 `k` 的非繼承屬性:
```js
> const obj = { foo: 123 };
> obj.hasOwnProperty('foo')
true
> obj.hasOwnProperty('bar')
false
```
但是,在一個對象的原型鏈中,可能會有另外一個屬性為 `hasOwnProperty` 的鍵,那會覆蓋掉在 `Object.prototype` 中的同名方法。然后調度的方法調用便不起作用:
```js
> const obj = { hasOwnProperty: true };
> obj.hasOwnProperty('bar')
TypeError: obj.hasOwnProperty is not a function
```
解決方法是使用直接方法調用:
```js
> Object.prototype.hasOwnProperty.call(obj, 'bar')
false
> Object.prototype.hasOwnProperty.call(obj, 'hasOwnProperty')
true
```
這種直接方法調用通常縮寫如下:
```js
> ({}).hasOwnProperty.call(obj, 'bar')
false
> ({}).hasOwnProperty.call(obj, 'hasOwnProperty')
true
```
這種模式似乎效率低下,但是大部分(JavaScript)引擎優化了這種模式,因此性能上不成問題。
#### 26.4.5 混入類(Mixin Classes)(高級)
JavaScript 的類系統僅支持*單繼承*。也就是說,每個類最多只能有一個父類。繞過這種限制的方法是通過稱為 *mixin 類*(簡稱: *mixins* )的技術。
這個想法如下:讓我們假設有一個類 `C` 想要繼承于兩個父類 `S1` 和 `S2`。這樣就會是多繼承,JavaScript 并不支持。
我們的解決方法便是把 `S1` 和 `S2` 轉成 `mixins`,即子類的工廠:
```js
const S1 = (Sup) => class extends Sup { /*···*/ };
const S2 = (Sup) => class extends Sup { /*···*/ };
```
這兩個函數中的每一個都返回一個繼承給定父類 `Sup` 的類。 我們創建C類如下:
```js
class C extends S2(S1(Object)) {
/*···*/
}
```
我們現在有一個類C,它繼承了一個類 S2,S2 繼承了一個繼承了 Object 的類 S1(大多數類都是隱含的)。
##### 26.4.5.1 示例:用于品牌管理的混入
我們實現一個混入類 `Branded`,它具有幫助方法來設置和獲取對象的品牌:
```js
const Branded = (Sup) => class extends Sup {
setBrand(brand) {
this._brand = brand;
return this;
}
getBrand() {
return this._brand;
}
};
```
我們使用這個mixin來實現類 `Car` 的品牌管理:
```js
class Car extends Branded(Object) {
constructor(model) {
super();
this._model = model;
}
toString() {
return `${this.getBrand()} ${this._model}`;
}
}
```
以下代碼確認 mixin 有效:`Car` 具有 `Branded` 的方法 `.setBrand()`。
```js
const modelT = new Car('Model T').setBrand('Ford');
assert.equal(modelT.toString(), 'Ford Model T');
```
##### 26.4.5.2 混入類(mixins)的好處
Mixins 讓我們擺脫單一繼承的束縛:
- 同一個類可以繼承單個父類和零個或多個 mixin。
- 多個類可以使用相同的mixin。
### 26.5 FAQ: 對象
#### 26.5.1 為什么對象保留屬性的插入順序?
原則上,對象是無序的。排序屬性的主要原因是列出條目,鍵或值的操作是確定性的。這有助于,比如,測試。
>  **測驗**
>
> 參見[測驗應用程序](/docs/11.md#91測驗)。
- I.背景
- 1.關于本書(ES2019 版)
- 2.常見問題:本書
- 3. JavaScript 的歷史和演變
- 4.常見問題:JavaScript
- II.第一步
- 5.概覽
- 6.語法
- 7.在控制臺上打印信息(console.*)
- 8.斷言 API
- 9.測驗和練習入門
- III.變量和值
- 10.變量和賦值
- 11.值
- 12.運算符
- IV.原始值
- 13.非值undefined和null
- 14.布爾值
- 15.數字
- 16. Math
- 17. Unicode - 簡要介紹(高級)
- 18.字符串
- 19.使用模板字面值和標記模板
- 20.符號
- V.控制流和數據流
- 21.控制流語句
- 22.異常處理
- 23.可調用值
- VI.模塊化
- 24.模塊
- 25.單個對象
- 26.原型鏈和類
- 七.集合
- 27.同步迭代
- 28.數組(Array)
- 29.類型化數組:處理二進制數據(高級)
- 30.映射(Map)
- 31. WeakMaps(WeakMap)
- 32.集(Set)
- 33. WeakSets(WeakSet)
- 34.解構
- 35.同步生成器(高級)
- 八.異步
- 36. JavaScript 中的異步編程
- 37.異步編程的 Promise
- 38.異步函數
- IX.更多標準庫
- 39.正則表達式(RegExp)
- 40.日期(Date)
- 41.創建和解析 JSON(JSON)
- 42.其余章節在哪里?