JavaScript 核心篇 學習筆記: Chap.56/57–使用 Object.create 建立多層繼承

Yi-Ning
7 min readFeb 24, 2020

--

某些情況下,我們會想要調整原型鍊的結構,在原型鍊上新增層級,便於一些共通屬性/方法的繼承,也可以讓原型鍊看起來更為完整。

Object.create()可以把物件作為原型使用

首先我們先來看一下Object.create()。它可以把物件作為原型使用,用它來建立新物件。例如下面這段程式碼,我們先定義一個物件實體Bibi,接著通過Object.create(),建立另一個物件變數Pupu

var Bibi = {
name: ‘比比’,
color: ‘棕色’,
size: ‘小’,
bark: function () {
console.log(this.name + ‘ 吠叫’);
}
}
var Pupu = Object.create(Bibi);
Pupu.name = ‘噗噗’;
console.log(Pupu);
console.log(Pupu.name);
console.log(Pupu.color);

在還沒定義新物件的屬性值前,新物件的屬性值會預設為原物件的屬性值(例如以上範例的Pupu.color)。

用Object.create()來新增層級

假設今天有一個Dog建構式,我們可以用它來建構許多原型為Dog的實體。但我們還想在Dog和原型鍊的頂點Object之間新增一層Animal原型:

function Animal(family) {
this.kingdom = ‘動物界’;
this.family = family || ‘人科’;
}
Animal.prototype.move = function() {
console.log(this.name + ‘ 移動’);
}
function Dog(name, color, size) {
this.name = name;
this.color = color || ‘白色’;
this.size = size || ‘小’;
}
// 這一行代表的是:Dog的原型繼承Animal的原型
Dog.prototype = Object.create(Animal.prototype);

Dog.prototype.bark = function() {
console.log(this.name + " 吠叫");
};
var Bibi = new Dog(‘比比’, ‘棕色’, ‘小’);
console.log(Bibi);
Bibi.bark();
Bibi.move();

如果把Bibi印出來看,會發現他並沒有Animal建構式裡定義的屬性(kingdom, family)。這是因為Dog只繼承了Animal的原型,但沒有繼承Animal的建構函式。必須透過call()Animal的建構函式帶進來。(注意call的第一個參數是指定this物件)

function Dog(name, color, size) {
Animal.call(this, “犬科”);
this.name = name;
this.color = color || “白色”;
this.size = size || “小”;
}

現在Bibi也有了原型Animal的屬性:

通過prototype.constructor把constructor補回來

接下來我們做的動作讓建構函式更完整。因為我們先前讓Dog的原型繼承Animal原型,也就代表了contructor被取代掉。因此,我們需要將Dogconstructor給補回來。

function Animal(family) {
this.kingdom = ‘動物界’;
this.family = family || ‘人科’;
}
Animal.prototype.move = function() {
console.log(this.name + ‘ 移動’);
}
function Dog(name, color, size) {
Animal.call(this, “犬科”);
this.name = name;
this.color = color || ‘白色’;
this.size = size || ‘小’;
}
// 這一行代表的是:Dog的原型繼承Animal的原型
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(this.name + " 吠叫");
};
var Bibi = new Dog(‘比比’, ‘棕色’, ‘小’);
console.log(Bibi);
Bibi.bark();
Bibi.move();

我們先透過另一個變數newAnimal了解constructor是什麼:

var newAnimal = new Animal(“新物種”);
console.log(newAnimal);

newAnimal的原型裡帶有一個constructor,它會指向原本的建構函式。只要是透過建構函式建立的物件,都可以在__proto__裡看到它原本的建構函式。

若沒有Dog.prototype.constructor = Dog這一行,我們會發現Dog原型裡的constructor不見了:

Dog.prototype = Object.create(Animal.prototype)Dogconstructor覆蓋掉,我們藉著Dog.prototype.constructor = Dogconstructor補回來。補回來之後的Bibi:

如果以一張圖來解釋Bibi和所有相關的原型之間的關係:

console.log(Bibi.__proto__ === Dog.prototype); // true
console.log(Bibi.__proto__.__proto__ === Animal.prototype); // true
console.log(Bibi.__proto__.__proto__.constructor === Animal); // true
console.log(Bibi.__proto__.__proto__.__proto__.__proto__ === null); // true

因為所有函式(這一章節提到的都是建構函式)都繼承函式原型,所以Dog, Animal, Object這些函式可以使用.prototype來建立原型。

console.log(Dog.__proto__ === Function.prototype); // true
console.log(Animal.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true

--

--