昨天提及我們可以用建構函式來建立實體物件,並讓該物件能夠繼承該建構函式所定義的屬性,以及繼承該建構函式的原型內的方法,從而形成一個原型鏈的結構。
在理解了原型和原型鏈的基本概念後,今天我們看看如何彈性修改原型鏈的結構,例如在原型鏈中間加插多一層建構函式,形成多層的原型鏈結構。
所以,接下來會舉例說明,用一些例子去:
//建構函式Car
function Car(type,color,person){
this.type = type;
this.color = color;
this.person = person;
}
//用Car產生實體物件
var myCar = new Car('電動車','紅色',4)
//目前原型鏈: Object > Car > myCar(實體)
這裏我們簡單建立了一個原型鏈,利用建構函式Car
產生一個實體物件myCar
。
目前的原型鏈是: Object > Car > myCar(實體)
然而,其實車是屬於交通工具的一種,所以在Object
和Car
之間,我們可以加插多一層,變成Object > Transportation > Car > myCar(實體)
。為什麼要加上Transportation
這一層?因為除了車以外,我們之後還可能要建立船、飛機等等,這些都是屬於交通工具,它們也可以繼承交通工具的屬性與方法。例如:
Object > Transportation > Ship > myShip
Object > Transportation > airplane > myAirplane
這時候,我們可以用Object.create()
的方法來加插多一層,具體操作就是先建立一個新的建構函式,再把將會排在較後的建構函式的原型(prototype),指向這個新建構函式的原型(prototype)
在以下範例,我們把先建立那一個我們想加插的建構函式(交通工具),並且在它的原型裏,加上drive
方法,因為所有交通工具都可以被駕駛:
//建立Transportation建構函式
function Transportation(transportType){
this.objectType = '交通工具';
this.transportType = transportType || '車';
}
//在Transportation建構函式的原型裏,新增drive方法
Transportation.prototype.drive = function(){
console.log('我可以駕駛' + this.type)
}
之後,使用Object.create
的方法,把Transportation
這個建構函式,放在Car
建構函式的上層:
//建立Transportation建構函式
function Transportation(transportType){
this.objectType = '交通工具';
this.transportType = transportType || '車';
}
//在Transportation建構函式的原型裏,新增drive方法
Transportation.prototype.drive = function(){
console.log('我可以駕駛' + this.type)
}
//Car建構函式裏的原型是繼承於Transportation的原型
Car.prototype = Object.create(Transportation.prototype)
之後我們才用Car
建構函式來建立實體物件myCar
。此外,我們也同時可以在Car
建構函式上新增方法carHorn()
,因為車子本身也可以響嗚:
//建立Transportation建構函式
function Transportation(transportType){
this.objectType = '交通工具';
this.transportType = transportType || '車';
}
//在Transportation建構函式的原型裏,新增drive方法
Transportation.prototype.drive = function(){
console.log('我可以駕駛' + this.type)
}
//Car建構函式裏的原型是繼承於Transportation的原型
Car.prototype = Object.create(Transportation.prototype)
//在Car建構函式裏的原型加上Car的車輛響嗚方法carHorn()
Car.prototype.carHorn = function(){
console.log(this.type + '發出車的嗚響')
}
//用Car產生實體物件myCar
var myCar = new Car('電動車','紅色',4)
console.log(myCar);
這時候在console
查看myCar
,下圖可見myCar
(由函式Car
建立出來的實體)的__proto__
的確繼承了Transportation
這個建構函式。注意,這裏我們是進行一個加插的動作,加插了Transportation
這一層在中間在myCar
與Object
之間。從頭到尾,我們都沒有用Transportation
來new
出Car
的建構函式。
另外有一點要重溫,就是實體物件只有__proto__
屬性,不同於建構函式同時擁有__proto__
和prototype
這兩個屬性。實體物件的__proto__
,會指向上一層的原型,即是上一層的prototype
,這個prototype
會放著:
constructor
(指回這一層它自己的建構函式)__proto__
(再找上一層的原型(prototype))因為myCar
這個實物目前不但繼承了Car
的原型,而且再繼承了Transportation
的原型,所以我們可以在myCar
同時使用carHorn()
,以及drive()
的方法:
myCar.carHorn(); //電動車發出車的嗚響
myCar.drive(); //我可以駕駛電動車
然而,如果我們在這時候查看myCar
,會發完我們還缺少了上面早期建立的Transportation
建構函式中,objectType
與transportType
這兩個屬性。此時它只有color
、person
和typr
這3個屬性:
這是因為Car
只繼承了Transportation
的原型(prototype),而沒有繼承Transportation
整個Transportation
的建構函式,所以我們要回到Car
的建構函式裏,補回Transportation
的建構函式,這樣才會吃到Transportation
函式內所有定義好的屬性。
//建構函式Car
function Car(type,color,person){
//在這裏補回Transportation的建構函式
//用this把Car和Transportation綁定在一起
Transportation.call(this, '車輛')
this.type = type;
this.color = color;
this.person = person;
}
這樣的話就成功補回objectType
和trnasportType
的屬性到myCar
實體物件裏:
還有一件事要做,就能大功告成。那就是把Car
的constructor
屬性補回來。因為現在Car
的原型是繼承Transportation
的原型,所以把Car
原本的constructor
屬性蓋掉,我們需要被它原本的constructor
(即是Car建構函式它自己)加回來:
//在Car建構函式的原型是繼承於Transportation的原型
Car.prototype = Object.create(Transportation.prototype)
//因為上一行程式,我把Car的原型設定為Transportation的原型,
//所以原本Car的原型裏的constructor被蓋掉
//但其實本身應該是要指向Car建構函式的本身
Car.prototype.constructor = Car
未加之前:
加回之後:
到這裏,我們已經完整了整個原型鏈的結構:
Object > Transportation > Car > myCar(實物)
我們可以用Object.create()
的方法,把一個建構函式加插在原型鏈的中間,達成一個建構函式繼承另一個建構函式的情況,從而達成多層的原型鏈。同時,也可以用.call
的方法,使我們在下層的建構函式取得到上層建構函式的屬性。
从__proto__和prototype来深入理解JS对象和原型链
JS核心篇(六角學院)