iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 17
2
Modern Web

JavaScript基本功修煉系列 第 17

JavaScript基本功修練:Day17 - 建立一個多層原型鏈

昨天提及我們可以用建構函式來建立實體物件,並讓該物件能夠繼承該建構函式所定義的屬性,以及繼承該建構函式的原型內的方法,從而形成一個原型鏈的結構。

在理解了原型和原型鏈的基本概念後,今天我們看看如何彈性修改原型鏈的結構,例如在原型鏈中間加插多一層建構函式,形成多層的原型鏈結構。

所以,接下來會舉例說明,用一些例子去:

  • 建立一個簡單的原型鏈
  • 在原型鏈中間加插多一層建構函式,讓它繼承上層的建構函式的屬性及原型裏方法,也同時會被下層繼承

建立一個簡單原型鏈

//建構函式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(實體)

加插多一層物件

然而,其實車是屬於交通工具的一種,所以在ObjectCar之間,我們可以加插多一層,變成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這一層在中間在myCarObject之間。從頭到尾,我們都沒有用TransportationnewCar的建構函式。

另外有一點要重溫,就是實體物件只有__proto__屬性,不同於建構函式同時擁有__proto__prototype這兩個屬性。實體物件的__proto__,會指向上一層的原型,即是上一層的prototype,這個prototype會放著:

  • constructor(指回這一層它自己的建構函式)
  • __proto__(再找上一層的原型(prototype))
  • 一些之前定義好的方法(如有)

因為myCar這個實物目前不但繼承了Car的原型,而且再繼承了Transportation的原型,所以我們可以在myCar同時使用carHorn(),以及drive()的方法:

myCar.carHorn(); //電動車發出車的嗚響
myCar.drive(); //我可以駕駛電動車

補回建構函式,使原型鏈更加完整

然而,如果我們在這時候查看myCar,會發完我們還缺少了上面早期建立的Transportation建構函式中,objectTypetransportType這兩個屬性。此時它只有colorpersontypr這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;
}

這樣的話就成功補回objectTypetrnasportType的屬性到myCar實體物件裏:

補回應有的constructor到Car的原型裏

還有一件事要做,就能大功告成。那就是把Carconstructor屬性補回來。因為現在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核心篇(六角學院)


上一篇
JavaScript基本功修練:Day16 - 原型的基本概念
下一篇
JavaScript基本功修練:Day18 - 用ES6的Class語法糖建立原型鏈
系列文
JavaScript基本功修煉31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
小碼農米爾
iT邦高手 1 級 ‧ 2020-10-17 09:54:47

這篇寫得真好,每個步驟都很清楚。

我要留言

立即登入留言