某些情況下,我們會想要調整原型鍊的結構,在原型鍊上新增層級,便於一些共通屬性/方法的繼承,也可以讓原型鍊看起來更為完整。
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
被取代掉。因此,我們需要將Dog
的constructor
給補回來。
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)
把Dog
的constructor
覆蓋掉,我們藉著Dog.prototype.constructor = Dog
把constructor
補回來。補回來之後的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