iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0
Software Development

從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!系列 第 11

Day 11 - 理解 JavaScript,為什麼要知道原型、原型鏈與原型繼承?

  • 分享至 

  • xImage
  •  

今天來點 JavaScript 的原型與繼承!

Prototype(原型)是什麼?

當我們在 JavaScript 中建立物件時,每個物件都有一個隱含屬性 [[Prototype]]
叫做 prototype(原型)。原型就像是物件的「模板」,決定了物件的一些基本特性和方法。

藉由以下例子,來看看 prototype__proto__

假設我們有一個建構式叫做 Person,我們用來建立一個人的物件。
這個 Person 建構函式有一個原型且此原型包含了一些方法和屬性。

function Person() { }

// 透過 prototype 方式,查看 Person 的原型
console.log(Person.prototype); // {constructor: ƒ}

當你在 console 印出 console.log(Person.prototype),你會看到:

{constructor: ƒ}
  constructor: ƒ Person()
  [[Prototype]]: Object

以上其實就顯示了 Person.prototype 物件的屬性和原型鏈。

{constructor: ƒ}:這表示 Person.prototype 物件本身的屬性。
在這裡,你看到一個 constructor 屬性,指向建構子 Person
這個屬性告訴 JavaScript,用來建立這個物件的建構子是 Person

constructor: ƒ Person():這是 constructor 屬性的具體內容。
顯示了 constructor 是一個函數,函數名稱是 Person
這表示這個物件是由 Person 建構函數建立的。

[[Prototype]]: Object:這表示 Person.prototype 物件的原型鏈。
Person.prototype 的原型是 Object
這是因為在 JavaScript 中,幾乎所有物件都繼承自 Object,因此 Object 是原型鏈的頂端。

在這個範例中,Person.prototype 繼承自 Object.prototype
constructor 屬性告訴我們與之關聯的建構子是 Person


接著,我們看一下透過 Person,建立一個 person1 物件:

function Person() { }

const person1 = new Person();

// 透過 __proto__ 方式,查看 person1 的原型
console.log(person1.__proto__); // {constructor: ƒ}

當我們使用 new Person() 建立一個具體的人的物件時,
這個物件會自動連接到 Person 建構函數的原型。
這意味著這個人的物件可以繼承(就像繼承父母的特徵一樣)原型中的方法和屬性。

這個概念有點像家族。
建構函式就像是家族的創始人,原型就像是家族的傳統,而物件就像是家族的成員,他們可以繼承和分享這些傳統。

透過 person1.__proto__Person.prototype 存取原型時,
你會看到一個對象,其中包含了 constructor 屬性,指向建立這個物件的建構子。

__proto__
__proto__ 是一個非標準的方法,通常用於訪問物件的原型,可以用来直接訪問和設置物件的原型。
注意!, __proto__ 方法並不在 ECMAScript 規範中,實際上要取得物件的原型會使用 Object.getPrototypeOf()


Prototype Chain(原型鏈)是什麼?

剛剛在 Person.prototype 有提及到原型鏈的概念,在這我們再一次加深印象!

Prototype Chain(原型鏈)是 JavaScript 中實作繼承和共享屬性/方法的重要機制。
原型鏈是一個由原型物件連接起來的鏈條,決定了在 JavaScript 中尋找屬性和方法時的搜索順序。
當你試圖呼叫一個物件的屬性或方法時,如果該物件本身沒有這個屬性或方法,
JavaScript 會沿著原型鏈向上查找,直到找到對應的屬性或方法,
或者達到原型鏈的末端 Object.prototype

Object.prototype,這是 JavaScript 中的所有物件原型的根。
如果在整個原型鏈上都找不到匹配的屬性或方法,JavaScript 將停止搜尋並傳回 undefined。

因此,透過剛剛建立的 Person 函式,再次觀察物件 person1 與建構函式 Person 之間的原型關係。

function Person() { }

const person1 = new Person();

// person1 物件可以透過 __proto__ 方法訪問到他的原型
person1.__proto__ === Person.prototype; // true
Object.getPrototypeOf(person1) === Person.prototype; // true
person1.__proto__ === Object.getPrototypeOf(person1); // true

Prototype Inheritance(原型繼承)是什麼?

原型繼承是 JavaScript 中的一種繼承機制,
允許一個物件繼承另一個物件的屬性和方法。
基於原型鏈的概念,子層物件的原型指向父層物件,
從而實現屬性和方法的共享和繼承。

讓我們來看看以下例子:

// 定義爸爸層 Person
function Person(name) {
  this.name = name;
}

// 在爸爸層原型上添加方法
Person.prototype.sayHello = function () {
  console.log("Hello, my name is " + this.name);
};

// 定義一個兒子層 Student
function Student(name, studentId) {
  // 使用爸爸層的構造函數,並傳遞名字
  Person.call(this, name);
  // 定義一個新的屬性,只有 Student 有的屬性
  this.studentId = studentId;
}

// 創建兒子層的原型鏈,繼承爸爸層的方法
Student.prototype = Object.create(Person.prototype);

// 指向自己的構造函數
Student.prototype.constructor = Student;

// 在兒子層原型上添加自己的 study 方法
Student.prototype.learn = function () {
  console.log(this.name + " is learning.");
};

// 創建爸爸層實例
var person = new Person("Viii");
person.sayHello(); // Hello, my name is Viii

// 創建兒子層實例
var student = new Student("Viii", "001");
student.sayHello(); // Hello, my name is Viii
student.learn();    // Viii is learning.
console.log(student.studentId); // 001

關於指向自己的構造函數:Student.prototype.constructor = Student;
如果建立一個 Student 實例,並嘗試訪問其建構函數,
將返回 Person 而不是 Student,這可能會導致混淆和不正確的行為。
為了更正這個問題,需要手動將 Student.prototype.constructor 設置為 Student,以確保指向正確的建構函數。這樣,當你建立 Student 的實例時,建構函數引用將正確指向 Student


Brief Summary

  • 每個物件都有一個原型,決定了物件的基本屬性和方法。
  • 原型鏈由原型物件構成,用於屬性和方法的查找。
  • 原型繼承是透過原型鏈來實現的,子層物件可以繼承父層物件的屬性和方法。

今天內容就到這裡啦!原型真的是一環扣一環呀~!

下篇要來點 ES6 的語法糖 Class!我們下篇見~!


參考資料:

文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)


上一篇
Day 10 - 理解 JavaScript,為什麼要知道如何建立物件?
下一篇
Day 12 - 理解 JavaScript,為什麼要知道 ES6 的語法糖 Class?
系列文
從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言