請描述原型繼承如何在 JavaScript 運作中?
原型 (Prototype)
在 JavaScript 中,每個物件都有一個內部屬性稱為 [[Prototype]],它指向該物件的原型。原型是一個物件,包含了可以被其他物件共享的屬性和方法。
__proto__
和[[Prototype]]
__proto__
:是大多數現代瀏覽器中暴露出的屬性,允許開發者存取和設置物件的原型(不建議直接使用),__proto__
目前已經棄用了。[[Prototype]]
:描述了物件與其 原型之間的隱藏式連結。
原型鏈(Prototype Chain)
原型鏈有往上查找的特性,當存取一個物件的屬性或方法時,Javascript 會先在物件本身尋找,如果找不到就會沿著原型鏈往上尋找,直到找到為止。原型鏈是由一系列物件組成的,它們通過 proto 屬性彼此相連,最終指向 Object.prototype
,原型鏈的末端。
//定義一個建構子
function Phone(brand, model) {
this.brand = brand;
this.model = model;
}
//在 Phone.prototype 上定義一個方法,所有 Phone 的實例都能訪問
phone.prototype.ring = function () {
console.log("Ring ring!");
};
//使用建構子建立一個物件
const myPhone = new Phone("Apple", "iPhone 16");
//存取物件的屬性
console.log(myPhone.brand); // Apple
//存取從原型繼承的方法
myPhone.ring(); // Ring ring!
//原型鏈
// myPhone.__proto__ 的原型是 Phone.prototype
console.log(myPhone.__proto__ === Phone.prototype); // true
// Phone.prototype 的原型是 Object.prototype
console.log(Phone.prototype.__proto__ === Object.prototype); // true
// Object.prototype 是原型鏈的終點 ,其原型為 null
console.log(Object.prototype.__proto__ === null); // true
在這個範例中,myPhone 是 Phone 建構函數的實例,它的 proto 屬性指向 Phone.prototype
,這樣它可以存取 Phone.prototype
上的 ring
方法。如果 Phone.prototype
上也找不到某個屬性或方法,JavaScript 會繼續沿著原型鏈向上查找,直到達到 Object.prototype
。
JavaScript 中的原型繼承是基於物件之間的關聯,每個物件都有一個原型物件,通過原型鏈的機制可以實現屬性和方法的繼承,可以實現一個物件從另一個物件繼承屬性和方法,實現程式碼的重用和擴展。
實例練習 1
// This is a JavaScript Quiz from BFE.dev
function Foo() {}
Foo.prototype.bar = 1;
const a = new Foo();
console.log(a.bar);
Foo.prototype.bar = 2;
const b = new Foo();
console.log(a.bar);
console.log(b.bar);
Foo.prototype = { bar: 3 };
const c = new Foo();
console.log(a.bar);
console.log(b.bar);
console.log(c.bar);
解題1
function Foo() {}
Foo.prototype.bar = 1;
const a = new Foo();
console.log(a.bar); //1,Foo.prototype.bar 定義了一個屬性 bar,a 繼承了這個屬性。
Foo.prototype.bar = 2;
const b = new Foo();
console.log(a.bar); //2,Foo.prototype.bar 被重新賦值為 2,a 和 b 都繼承了這個屬性。
console.log(b.bar); //2,由於 a 的原型鏈指向 Foo.prototype,所以 a.bar 也變成了 2。
Foo.prototype = { bar: 3 };
const c = new Foo();
console.log(a.bar); //2 ,a 的原型鏈仍然指向舊的 Foo.prototype,所以 a.bar 仍然是 2。
console.log(b.bar); //2,b 的原型鏈仍然指向舊的 Foo.prototype,所以 b.bar 仍然是 2。
console.log(c.bar); //3,Foo.prototype 被重新賦值為一個新物件,c 繼承了這個新物件。
實例練習 2
// This is a JavaScript Quiz from BFE.dev
function F() {
this.foo = "bar";
}
const f = new F();
console.log(f.prototype);
解題2
// This is a JavaScript Quiz from BFE.dev
function F() {
this.foo = "bar";
}
const f = new F();
console.log(f.prototype); //undefined,f 是 F 的實例,而不是 F 的原型