<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 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. **子類化:** *子類* 與其 *父類* 之間的關系也是基于原型繼承。 ![Figure 9: This book introduces object-oriented programming in JavaScript in four steps.](https://img.kancloud.cn/1a/0b/1a0ba3de24e2c5744efc517b652f7f19.svg) 圖 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`的原型鏈是什么樣的。 ![Figure 10: obj starts a chain of objects that continues with proto and other objects.](https://img.kancloud.cn/a7/15/a715246e7be0d1adf99558f52e342371.svg) 圖 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) 。 ![Figure 11: The own property .protoProp of obj overrides the property inherited from proto.](https://img.kancloud.cn/c2/77/c277d6784709cef108b38d5d0f0a4a2c.svg) 圖 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`都是人類。 ![Figure 12: Objects jane and tarzan share method .describe(), via their common prototype PersonProto.](https://img.kancloud.cn/a9/70/a9701e682e640358c3e172405b6411cf.svg) 圖 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) )。 ![Figure 13: The class Person has the property .prototype that points to an object that is the prototype of all instances of Person. jane is one such instance.](https://img.kancloud.cn/e9/72/e972438fced4c7e70a91aae455cdfe77.svg) 圖 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` 中創建了一個方法。 - 類即函數。 >![](https://img.kancloud.cn/3e/d5/3ed5755d562179ae6c199264f5e21157.svg) **練習:實現一個類** > > `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 ``` > ![](https://img.kancloud.cn/3e/d5/3ed5755d562179ae6c199264f5e21157.svg) **練習:子類化** > > `exercises/proto-chains-classes/color_point_class_test.js` #### 26.4.1 內部的子類(高級) ![Figure 14: These are the objects that make up class Person and its subclass, Employee. The left column is about classes. The right column is about the Employee instance jane and its prototype chain.](https://img.kancloud.cn/8a/c1/8ac151db65cd7126d35b0c1a68488c72.svg) 圖 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 ``` ![Figure 15: The prototype chain of an object created via an object literal starts with that object, continues with Object.prototype and ends with null.](https://img.kancloud.cn/35/27/35274703c646a80e4f09449a24427044.svg) 圖 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 ``` ![Figure 16: The prototype chain of an Array has these members: the Array instance, Array.prototype, Object.prototype, null.](https://img.kancloud.cn/38/a8/38a8e520b9d33dc54a29225d912daca6.svg) 圖 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`原型鏈的圖表。 ![Figure 17: The prototype chain of jane starts with jane and continues with Person.prototype.](https://img.kancloud.cn/dc/a6/dca6fd102cffca87274727e16fb5ce5a.svg) 圖 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 為什么對象保留屬性的插入順序? 原則上,對象是無序的。排序屬性的主要原因是列出條目,鍵或值的操作是確定性的。這有助于,比如,測試。 > ![](https://img.kancloud.cn/ff/a8/ffa8e16628cad59b09c786b836722faa.svg) **測驗** > > 參見[測驗應用程序](/docs/11.md#91測驗)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看