iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 24
0
Modern Web

教練我想學 JavaScript 系列 第 24

Day 24 Class 和 Prototype 繼承

接下來開始會談到 JavaScript 的物件導向(Object Oriented)和原型繼承(Prototype Inheritance)的觀念,
當談到物件導向時會聚焦在物件的創造,

繼承是在說一個物件能夠取用到另一個物件的屬性或方法,

和其他程式語言(C#、Java)不同,
其他程式語言在實現繼承時,是透過 Class 來達成的,
這可能造成物件繼承的屬性和方法過於龐大,到最後會很難維護,而且比較不好理解,
但這也不是說透過 Class 的繼承方式不好,畢竟每個語言都有自己的一套脈絡,而且有非常多人在使用,

而 JavaScript 是透過 Prototype 來實現繼承的,
這經過簡化,比較容易學習,具有彈性和可擴充性,

這其實需要從 JavaScript 被發明的時空背景說起,詳細這段內容可以參考這邊的連結

那麼在 JavaScritp 中到底是怎樣透過原型(Prototype)屬性來取用另一個物件的屬性和方法的,
假設我們有一個物件 obj 在記憶體中,在 obj 物件裡有個 prop1 屬性,所以我們可以用點運算子取用到 prop1 屬性,
如下圖:

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 5 節講座 54 影片截圖

JavaScript 在物件中都有隱藏的屬性和方法,
所以物件(包含函數)都有個原型屬性(物件),
物件 obj 可以取用到原型屬性(物件)的屬性和方法,
如下圖:

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 5 節講座 54 影片截圖

原型屬性(物件)有個 prop2 的屬性,當我想在物件 obj 直接透過點運算子取用時找不到,
所以會到物件 obj 的原型屬性(物件)裡面找,
因為原型屬性(物件)有 prop2 屬性,所以物件 obj 可以取用到 prop2 屬性,回傳 prop2 的值
如下圖:

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 5 節講座 54 影片截圖

物件 obj 的原型屬性(物件)也可以有自己的原型屬性(物件),
在原型屬性(物件)的原型屬性(物件)也有個 prop3 屬性,
我也可以直接在透過點運算子參考到 prop3 屬性,回傳 prop3 屬性所在的記憶體空間的值,
因為點運算子在物件 obj 找不到 prop3 屬性,所以會往物件 obj 的原型屬性找,
在原型屬性裡找不到,會往原型屬性自己的原型屬性找,
在原型屬性自己的原型屬性裡面發現有 prop3 屬性,
所以最後可以取用到 prop3 屬性並回傳給我們 prop3 的值,
如下圖:

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 5 節講座 54 影片截圖

取用 obj.prop3 時看起來很像直接在物件 obj 上取得 prop3 屬性的回傳值,
但實際上是在原型鏈裡找到的,
原型鏈會連結每一個原型屬性(物件),
就好像物件 obj 是祖父,物件 obj 的原型屬性(物件)是自己的兒子,
原型屬性(物件)可以有自己的原型屬性(物件),也就是物件 obj 的孫子,
當爺爺想要知道孫子的名字時可以透過詢問孫子的爸爸得知,
如下圖:

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 5 節講座 54 影片截圖

不要把原型鏈跟範圍鏈搞錯了,
範圍鏈是透過外部參考取用變數,
原型鏈是取用其他物件的屬性和方法,

我不用像 obj.proto.proto.prop3 這樣來取用在原型屬性(物件)裡的屬性或方法,
因為這是隱藏起來的,JavaScript 引擎會到原型鏈搜尋屬性和方法,

如果我有另外一個物件 obj2 ,我可以與物件 obj 共用同一個原型屬性(物件),但不是直接的,
prop2 屬性不在物件 obj 和物件 obj2 上,所以會到原型練上搜尋,發現他們指向同一個原型屬性(物件),
所以可以取得原型屬性(物件)裡面的屬性或方法,
如下圖:

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 5 節講座 54 影片截圖

他們參考同一個原型屬性(物件)的屬性的同一個記憶體位置,
如下圖:
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 5 節講座 54 影片截圖

接著我們來看範例,
程式碼如下:

var person = {
  firstname: 'Default',
  lastname: 'Default',
  getFullName: function() {
    return this.firstname + ' ' + this.lastname;
  }
}

var jimmy = {
  firstname: 'jimmy',
  lastname: 'huang'
}

現在我新增兩個類似的物件,只是其中一個物件沒有 getFullName 方法,

記得我們在講函數時透過函數本身的 bind 方法來改變執行函數時的關鍵字 this 指向的物件嗎?
透過函數的 bind 、call 、apply 方法我們可以跟其他函數借用屬性或方法,

物件可以透過原型屬性(物件)來取用其他物件的屬性和方法,
如果我們到 Console 中察看這兩個物件的屬性,
會發現兩個物件都有個隱藏的屬性 __proto__,如下圖:


這就是物件的原型屬性(物件),瀏覽器提供我們快速取用原型屬性,
也就是在瀏覽器中物件的__proto__屬性,
可以用來取用其他物件的屬性或方法,
但實際開發上儘量不要去改動到物件的__proto__屬性,
這邊只是為了方便解釋與展示,
我們將程式碼修改下,
程式碼如下,

var person = {
  firstname: 'Default',
  lastname: 'Default',
  getFullName: function() {
    return this.firstname + ' ' + this.lastname;
  }
}

var jimmy = {
  firstname: 'jimmy',
  lastname: 'huang'
}

// 取用__proto__只是為了展示方便,實際開發上絕對不要這樣做!!!!!
// 取用__proto__只是為了展示方便,實際開發上絕對不要這樣做!!!!!
// 取用__proto__只是為了展示方便,實際開發上絕對不要這樣做!!!!!
jimmy.__proto__ = person;

console.log(jimmy.getFullName());

透過將物件 person 指派給__proto__屬性,這樣物件jimmy 在自己的屬性取用 getFullName 方法時雖然找不到,
但因為 JavaScript 引擎會到原型鏈查找連結的原型屬性中是否有,
現在因為物件 jimmy 的原型屬性(物件)跟 person 指向同一個記憶體位址,所以可以取用到物件 person 的屬性和方法,

要注意的是,因為我們是透過物件 jimmy 來呼叫 getFullName 方法的,所以現在的 this 是指向物件 jimmy,
因此取用到的屬性是物件 jimmy 本身的屬性,
所以在 Console 中會得到以下結果:

我們在新增一個物件 jane,
這次只新增一個屬性 firstname,
將物件 jane 的原型屬性(物件)__proto__透過等號(指派)運算子設成物件 person,
這樣它們會參考到相同的記憶體位址,

程式碼如下:

var person = {
  firstname: 'Default',
  lastname: 'Default',
  getFullName: function() {
    return this.firstname + ' ' + this.lastname;
  }
}

var jimmy = {
  firstname: 'jimmy',
  lastname: 'huang'
}

jimmy.__proto__ = person;

var jane = {
  firstname: 'jane'
}

jane.__proto__ = person;

console.log(jane.getFullName());

在透過參考原型屬性(物件)的 getFullName 方法時,結果會呈現什麼?

在 Console 中的結果如下:

為什麼會輸出這個結果,而不是兩個 Default?
因為物件 jane 有自己的屬性 firstname ,如果在物件中已經有這個屬性就不會在往原型屬性(物件)找,
但沒有屬性lastname ,所以會往原型鏈找,在原型鏈下找到原型屬性(物件)有 lastname 屬性並回傳 ,
注意因為是用物件 jane 呼叫 getFullName 方法的,所以呼叫 getFullName 方法時的 this 指向物件 jane 。

補充說明:
因為這堂課是在 ES6 推出之前錄製的,ES6 當時還沒正式推出,
現在 ES6 已經症式推出 Class 的語法糖,
可以讓我們像在其他程式語言(C#、Java)中使用 Class,
可以參考這個連結


上一篇
Day 23 函數程式設計
下一篇
Day 25 物件、Reflection 與 Extend
系列文
教練我想學 JavaScript 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言