什麼是原型鏈?
原型鏈是 JavaScript 實作繼承
的機制,嚴格說起來就是一層接一層的概念!
每個物件都有一個 [[Prototype]]
屬性,可以透過 __proto__
存取,這個屬性指向另一個對象,通常是該物件的原型。
如果物件本身沒有某個屬性,它會從它的原型物件上找出這個屬性;如果原型物件上也沒有這個屬性,它會繼續沿著原型鏈向上查找,直到找到屬性或到達原型鏈的末端,通常是 Object.prototype
,原型為 null
。
基本概念
[[Prototype]]
內部屬性(通常可以透過 __proto__
或 Object.getPrototypeOf()
存取),指向其原型物件。// function 也是物件,所以也有自己的原型鏈
function test() {
console.log(1);
}
console.log(test.prototype);
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
逐步看一下繼承的概念:
Object.create
建立一個新的對象 rabbit,繼承自 animal,並且賦予 rabbit 有跳的功能Object.getPrototypeOf
可取得物件的 [[Prototype]]
的值,先看 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
是指向當前對象實例的引用,簡單來說,誰呼叫this
,this
就是誰!
在類別方法中使用this
時,因為引用的是該方法被調用的對象實例,也就是新創建的Animal
實例,所以this.name
會指向那個創建的人,例如:myDag
是創建的人,所以調用實例,this.name
在Animal
類別的上下文中,指的是當前對象實例myDag
的name
屬性狗狗
。
原型的特性
原型繼承 vs 類別繼承