今天來點 JavaScript 的原型與繼承!
當我們在 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()
。
剛剛在 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
原型繼承是 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
。
今天內容就到這裡啦!原型真的是一環扣一環呀~!
參考資料:
文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)