--- title: '核心 24 - 使用 Object.create 建立多層繼承' tags: JS 核心 ,JS , JavaScript description: 2021/02/19 --- JS 核心 -- 使用 Object.create 建立多層繼承 === ## 原型鏈結構 ### :pencil2: 實體可取用物件原型、陣列原型上的方法。 實體繼承在兩者之下 a (陣列實體) 可取用 Array 原型、Object 原型的所有方法 > 原型鏈 : Object 原型 > Array 原型 > a (陣列實體) ``` var a = []; // a 是一個陣列實體 ``` ### :pencil2: 使用建構函式建立 Dog 原型,透過他來建立一個建構式,產生 “比比” 的狗 > 原型鏈 : Object 原型 > Dog 原型 > Bibi (實體) ``` function Dog(name, color, size) { this.name = name; // 使用 this 將這些屬性綁定在他們自己身上 this.color = color; this.size = size; } var Bibi = new Dog('比比', '棕色', '小'); // 透過 new 運算子產生新物件 ``` ## Object.create () : 在原型鏈上新增新的層級 ### <span class="red">Object.create () 方法 : 可以把其他物件作為原型使用</span> > **透過 Object.create() 可以建立一個空物件,同時可以將你帶入 Object.create() 的參數內容變成該物件的原型。** #### 建立好的狗物件 ``` var Bibi = { name: '比比', color: '棕色', size: '小', bark: function () { console.log(this.name + ' 吠叫'); // this 指 Bibi 這個物件 } } ``` #### 產生新狗 Pupu,透過 Object create ( )方式繼承在 ’比比’ 之下 > 原型鍊的頂點 Object > Bibi 原型 > Pupu 實體 ``` var Pupu = Object.create(Bibi); // 把 Bibi 作為原型使用 console.log(Pupu); // { } (目前 Pupu 是空物件,把 Bibi 作為原型使用) console.log(Pupu.name); // '比比' Pupu.name = '噗噗';    // 為 Pupu 這個空物件賦予屬性或方法 console.log(Pupu); // {name: '噗噗'} (展開如下圖) console.log(Pupu.name); // '噗噗' ``` * Pupu 繼承 Bibi 的一些方法 * 在還沒定義新物件的屬性值前,新物件的屬性值會預設為原物件的屬性值 * 在不改變屬性的情況下,所有值都可以以 Bibi 做為預設值 * 創造多層原型鏈時,使用 Object.create ( ) 方式 ![](https://i.imgur.com/UmU3iLX.png) ### :pencil2: 範例 - 在原型鏈上新增新的層級 假設今天有一個 Dog 建構式,我們可以用它來建構許多原型為 Dog 的實體。但我們還想在 Dog 和原型鍊的頂點 Object 之間新增一層 Animal 原型。 > 原型鍊的頂點 Object > Animal 原型(建構式) > Dog 原型(建構式) > Dog 實體 > 原型鍊的頂點 Object > Animal 原型(建構式) > Cat 原型(建構式) > Cat 實體 (第 12 行) 1. 把 Animal 的建構函式帶近來,this 指向 animal,後面帶入參數。(call 的第一個參數是指定 this 物件) 2. 物件本身具有傳遞參考的特性 3. 在此的 this 透過 call 傳入後就會使用 new 所產生的 Object。因此執行時,就會將同一個 this 加上 kingdom, family 等屬性 4. new object 在這裡呼叫 Animal, 並指定 Animal 的 this 為自己的 this, 所以 new object 才會具備 animal 與 dog 的屬性 5. 原本 Dog 的 this 帶入,透過 this 把 Animal 和 Dog 綁在一起,並傳入科別。這裡的 this 是 Dog 與 Animal 共用。 ```typescript= function Animal(family) { // 創立一個 Animal 建構函式,可以傳入科別 this.kingdom = '動物界'; this.family = family || '人科'; } Animal.prototype.move = function() { // 在 Animal.prototype 新增方法 console.log(this.name + ' 移動'); } function Dog(name, color, size) { console.log('this:', this); // 當執行到此行時,this 並不包含犬科。 只有繼承動物界的原型,沒有繼承整個動物界的建構函式 Animal.call(this, '犬科'); // 加入了 Animal 建構式裡定義的屬性(kingdom, family) // (和 18 行呼應)定義科別,透過 call 方法把 Animal 的建構函式帶入 this.name = name; this.color = color || '白色'; this.size = size || '小'; } Dog.prototype = Object.create(Animal.prototype); // Dog 的原型繼承 Animal 的原型 // 只有繼承動物界的原型,但是沒有繼承 Animal 的建構函式 (要使用 Animal.call 帶入)(12 行) // 因為把狗原型直接繼承在動物界的原型,constructor 會被覆蓋掉,把 constructor 補回 Dog.prototype.constructor = Dog; // 只有繼承動物界的原型,constructor 被覆蓋要再加回來 Dog.prototype.bark = function () { console.log(this.name + ' 吠叫'); } var Bibi = new Dog('比比', '棕色', '小'); console.log(Bibi); // (如下圖) Bibi.bark(); // 比比 吠叫 Bibi.move(); // 比比 移動 (繼承於 Animal 的原型下) function Cat(name, color, size) { Animal.call(this, '貓科'); this.name = name; this.color = color || '白色'; this.size = size || '小'; } Cat.prototype = Object.create(Animal.prototype); Cat.prototype.constructor = Cat; Cat.prototype.meow = function () { console.log(this.name + ' 喵喵叫'); } var Kity = new Cat('凱蒂'); Kity.meow(); // 凱蒂喵喵叫 Kity.move(); // 凱蒂移動 Kity.bark(); // Kity.bark is not a function ``` ![](https://i.imgur.com/Alu737b.png) 若沒有 Dog.prototype.constructor = Dog 這一行,我們會發現 Dog 原型裡的 constructor 不見了 (如上左圖) Dog.prototype = Object.create(Animal.prototype) 把 Dog 的 constructor 覆蓋掉,我們藉著 Dog.prototype.constructor = Dog 把 constructor 補回來。補回來之後的 Bibi (如上右圖) constructor 補回的用意是讓建構函式的結構(屬性及內容)更為正確。不過實際上,結構不正確只會影響未來的除錯或反查功能,並不會影響運行(但就結構正確性來說,還是建議加上)。 ### 小雷點 若把 (22-24 行)和(18 行)交換會跳錯。 若將 Dog 繼承於 Animal 之下的 create,放在吠叫這個動作的函式下面,如下 : ``` Dog.prototype.bark = function () { // (22-24 行) console.log(this.name + ' 吠叫'); } Dog.prototype = Object.create(Animal.prototype); // (18 行) Bibi.bark(); // 跳錯,Bibi.bark is not a function ``` Dog.prototype 會先執行建立賦值 bark,然後後面執行 Object.create(Animal.prototype) 也就是重新賦予值的動作,所以執行 bark( ) 會出現錯誤。 ## 什麼是 constructor 使用建構式來產生新物件時,物件原型內的 constructor 就會指向原本的建構函式;正確標示該物件的產生函式。 (constructor 自動產生。) ``` function Animal(family) { // 創立一個建構函式 this.kingdom = '動物界'; this.family = family || '人科'; } var newAnimal = new Animal('新物種'); console.log(newAnimal); // (如下圖) ``` ![](https://i.imgur.com/ugQN1dp.png) --- ## 原型鏈、建構函式整體結構概念 ![](https://i.imgur.com/SP1H1RB.png) ### ☞ 建立實體的架構表 - 解釋 Bibi 和所有相關的原型之間的關係 ![](https://i.imgur.com/JwljlaT.png) <span class="green">**綠字 : 建構函式**</span> <span class="red">**紅線 : 原型鍊架構**</span> _ _proto_ _ : 原型鍊 (不斷向上查找) constructor : 連結到原本的建構函式 > 每個原型都有自己的建構方法,每個原型都有自己的 constructor,constructor 顯示原本的建構函式 目前有 Dog、Animal、Object 這些建構函式,透過建構函式就可以來產生他們的原型,使用 Dog.prototype 的方法就可以產生相對應的原型 <span class="green">**( 綠線 )**</span>。 Q : 為什麼 Dog、Animal、Object 這些建構函式可以使用 Dog.prototype 的方法呢 ? A : 因為 Dog、Animal、Object 這些建構函式也有屬於他們自己的原型,同樣都是繼承於 Function 的原型下,連向 Function.prototype <span class="blue">**( 藍線 )**</span>。 Function 原型也有屬於它自己的建構方式。 (最右方圖示) <span class="red">**最頂層的原型物件: Object.prototype**</span>,Object.prototype 是 JavaScript 所有物件的起源。 ### :pencil2: 驗證原型鍊的概念 (承上例程式碼) ``` // 以下皆為 true console.log(Bibi.__proto__ === Dog.prototype); console.log(Bibi.__proto__.__proto__ === Animal.prototype); console.log(Bibi.__proto__.__proto__.constructor === Animal); // 往上二層的建構器是 Animal console.log(Bibi.__proto__.__proto__.__proto__.__proto__ === null); ``` ### :pencil2: 驗證建構函式為什麼可以使用 prototype 的方法 <span class="red">**所有的「建構函式」都繼承於 Function.prototype (函式的原型)**</span> ``` console.log(Dog.__proto__ === Function.prototype); // true console.log(Animal.__proto__ === Function.prototype); // true console.log(Object.__proto__ === Function.prototype); // true console.log(Function.__proto__ === Function.prototype); // true console.log(Function.__proto__.__proto__ === Object.prototype); // true ``` --- ## :memo: 學習回顧 :::info * <span class="red">所有的建構函式都繼承函式原型,所以 Dog, Animal, Object 這些函式可以使用 .prototype 來建立原型。</span> * (Object.__proto__ === Function.prototype); * (Function.__proto__ === Function.prototype) * (Function.__proto__.__proto__ === Object.prototype) ::: <style> .red { color: red; } .green { color: green; } .blue { color: blue; } </style>