iT邦幫忙

2024 iThome 鐵人賽

DAY 15
0
JavaScript

30天 JavaScript 提升計畫:從零到精通結合2024年的創新功能系列 第 15

第 15 天:JavaScript 的原型鏈與繼承

  • 分享至 

  • xImage
  •  

原型鏈的工作原理


MDN:繼承與原型鏈物件原型

什麼是原型鏈?
原型鏈是 JavaScript 實作繼承的機制,嚴格說起來就是一層接一層的概念!

每個物件都有一個 [[Prototype]] 屬性,可以透過 __proto__ 存取,這個屬性指向另一個對象,通常是該物件的原型。

如果物件本身沒有某個屬性,它會從它的原型物件上找出這個屬性;如果原型物件上也沒有這個屬性,它會繼續沿著原型鏈向上查找,直到找到屬性或到達原型鏈的末端,通常是 Object.prototype,原型為 null
原型鏈

基本概念

  1. 物件的原型:
  • 每個 JavaScript 物件都有一個 [[Prototype]] 內部屬性(通常可以透過 __proto__Object.getPrototypeOf() 存取),指向其原型物件。
  • 原型物件也有自己的原型,這形成了一個鏈式結構,稱為原型鏈。
    // function 也是物件,所以也有自己的原型鏈
    function test() {
     console.log(1);
    }
    
    console.log(test.prototype);
    
    test.prototype
  1. 屬性查找:
    存取一個物件的屬性時,JavaScript 首先會在物件本身找到。如果物件本身沒有該屬性,JavaScript 會沿著原型鏈向上查找,直到找到該屬性或到達原型鏈的末端(null)。

範例說明

// 創建一個對象
const person = {
    name: 'kuku',
    greet() {
        console.log('Hello!');
    }
};

// 創建一個新的對象,因為原型是 person,所以繼承了 person 物件的 name 屬性和 greet 方法
const employee = Object.create(person);
employee.jobTitle = 'front_end_eng.';

// 訪問屬性和方法
console.log(employee.name);           // 'kuku',從原型鏈中尋找
console.log(employee.jobTitle);       // 'front_end_eng.',在物件本身尋找
employee.greet();                     // 'Hello!',從原型鏈中尋找

原型繼承

原型繼承與類別 class 繼承


看到這有沒有感覺很奇怪?為什麼不能宣告一個新的變數重新賦值就好了?還要用 Object.create() 或建構子來建立一個新對象? Let's try it...

const person = {
    name: 'kuku',
    greet() {
        console.log('Hello!');
    }
};

const employee = person;
employee.name = 'monica';
console.log(`person.name -> ${person.name}`, `employee.name -> ${employee.name}`);

打印結果
明明重新賦值的是 employee.name,但是 person.name 也被改了,因為這個方法不只沒有新物件被建立,記憶體還指向了 person,所以這個方法沒有辦法實現「原型」的概念,接下來我們來說說如何原型繼承又不會互相影響。

原型繼承
原型繼承是透過設定物件的原型來實現繼承,所以可以使用 Object.create() 或建構子來建立一個新對象,並設定其原型。

const animal = {
    makeSound() {
        console.log('Some sound');
    }
};

// 創建一個新的對象,因為原型是 animal,所以繼承了 animal 物件的方法,並且改寫
const dog = Object.create(animal);
dog.makeSound = function () {
    console.log('Bark');
};

dog.makeSound();      // 'Bark'

或許還有些模糊,那改用 console.log 逐步看一下繼承的概念:

  1. 建立一個對象 animal
  2. 再用 Object.create 建立一個新的對象 rabbit,繼承自 animal,並且賦予 rabbit 有跳的功能
  3. 運用 Object.getPrototypeOf 可取得物件的 [[Prototype]] 的值,先看 rabbit 自己的原型,再看原型的原型
  4. 最後看 rabbit 的原型和繼承屬性,如果想要知道到底屬性來自於誰,可以用 hasOwnProperty 檢查屬性是屬於當前物件,還是位於原型串鏈中
const animal = {
    eats: true
};

const rabbit = Object.create(animal);
rabbit.jumps = true;

console.log('rabbit 的原型', Object.getPrototypeOf(rabbit));
console.log('rabbit 的原型的原型', Object.getPrototypeOf(Object.getPrototypeOf(rabbit)));

console.log('rabbit 的屬性', rabbit.jumps);
console.log('rabbit 來自原型鏈的屬性', rabbit.eats);

原型繼承

類別繼承

MDN 文件:Classes

類別繼承是一種基於「類別」的繼承機制。

類別是一個藍圖,它定義了對象的屬性和方法,當我們創建一個新類別時,可以從已經存在的類別繼承屬性和方法,並且可以擴展或修改這些功能。

在 ES6 及更高版本中,JavaScript 引入了 class 語法,讓建立和繼承類別變得更加直覺,當然 class 底層仍然基於原型繼承,但這種語法糖簡化了物件和繼承的創建過程。

/**
 * 定義基礎類別 class,內容包含了一些通用的屬性和行為
 * 比如 Animal 動物基本上都會「吃東西」和「睡覺」
 * 
 */
class Animal {
    constructor(name) {
        this.name = name;
    }

    eat() {
        console.log(`${this.name} is eating.`);
    }

    sleep() {
        console.log(`${this.name} is sleeping.`);
    }
}

/**
 * 定義繼承自 Animal 的子類 Dog,並且建立屬於狗的獨特的特徵
 * Dog 被視為 Animal 類別的具體實現,繼承了 Animal 類別的通用特性,並且擴展了 Dog 特殊行為
 * 
 */
class Dog extends Animal {
    bark() {
        console.log(`${this.name} is barking.`);
    }
}

/**
 * 定義繼承自 Animal 的子類 Cat,並且建立屬於貓的獨特的特徵
 * Cat 被視為 Animal 類別的具體實現,繼承了 Animal 類別的通用特性,並且擴展了 Cat 的特殊行為
 * 
 */
class Cat extends Animal {
    meow() {
        console.log(`${this.name} is meowing.`);
    }
}

// 創建 Dog 和 Cat 的實例
const myDog = new Dog('狗狗');
const myCat = new Cat('喵喵');

// 調用基礎類別和子類別的方法
myDog.eat();    // 狗狗 is eating.
myDog.sleep();  // 狗狗 is sleeping.
myDog.bark();   // 狗狗 is barking.

myCat.eat();    // 喵喵 is eating.
myCat.sleep();  // 喵喵 is sleeping.
myCat.meow();   // 喵喵 is meowing.

🔔 補充說明:
在 JavaScript 的類別定義中,this 是指向當前對象實例的引用,簡單來說,誰呼叫 thisthis就是誰!
在類別方法中使用 this 時,因為引用的是該方法被調用的對象實例,也就是新創建的 Animal 實例,所以 this.name 會指向那個創建的人,例如:myDag 是創建的人,所以調用實例,this.nameAnimal 類別的上下文中,指的是當前對象實例 myDagname 屬性狗狗

總結


原型的特性

  • 原型一樣具有物件的特性:也就是說,這個原型一樣具有屬性和方法
  • 原型有向上查找的特性:繼承的物件可以透過向上查找,查到原型的方法和屬性
  • 原型可以共用方法和屬性:如果多個實體都是繼承同一個原型的話,那麼原型在更新的時候,實際上所取到的原型屬性和方法,也都會是更新後的結果。

原型繼承 vs 類別繼承

  • 類別繼承使用明確的類別和繼承關係,通常需要顯式地定義類別及其子類。
  • 原型繼承則是基於對象和原型鏈的動態關係,對象可以直接從其他對象繼承屬性和方法。

上一篇
第 14 天:2024 年 JavaScript 新特性 - 類型檢查
下一篇
第 16 天:建構子函數
系列文
30天 JavaScript 提升計畫:從零到精通結合2024年的創新功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言