JavaScript 程式執行原理:JS中的物件導向


Posted by backas36 on 2021-08-16

在 ES6 以前,還沒有 class 可以來創建一個類別,類別可以想像成定義一個 object 的設計稿,我們可以透過 class 來描述一個 object 長什麼樣子,通常這個 object 我們稱為 instance,例如你創建了一個手機的設計稿,設計稿裡面有描述手機該叫什麼名字,有什麼功能可以使用,而當我們去規範這個一支手機叫 'iPhone',並且有打電話這功能,那這支手機我們稱為 instance。

即使在 ES6 以前沒有 class 可以使用,我們仍然 function 的方式去實現物件導向的概念,或許之前我們已經常使用類似的概念的,可能我們沒有發現而已。

以下是簡單的例子:

function Phone(name){
    var myName = name
    return {
        getName: function(){
            return myName
        },
        calling: function() {
            console.log(myName + ' => call someone...')
        }
    }
}

var iPhone = Phone('iPhone')
iPhone.calling()

var s3 = Phone('S3')
s3.calling()

我們創建了兩支手機,iPhone 和 S3,並且他們都有打calling的 method 可以使用。所以理論上 iPhone.calling === s3.calling 應該要是 true 才對,但其實會回傳 false,由此可知當我們創建了一萬支手機,我們就會創建了一萬次 calling 這 method,這不符合邏輯,因為所有手機應該都要共用 calling。

所以我們改寫了一下程式:

function Phone(name){
    return this.name = name
}

Phone.prototype.getName = function(){
    return this.name
}

Phone.prototype.calling = function(){
    console.log(this.name + ' => call someone...')
}

var iPhone = new Phone('iPhone')
iPhone.calling()

var s3 = new Phone('S3')
s3.calling()

注意到我們使用了關鍵字 new 來新增一個 instance,還有利用 prototype 來創建共用的 method,並且現在 iPhone.calling === s3.calling 就是 true 了。

其實每當我們新增一個實例的時候 JS 會幫我們建立 __proto__,而這個 __proto__ 就是 Phone 的 prototype

iPhone.__proto__ === Phone.prototype //true

當我們輸入 iPhone.calling() 的時候會發生一些事:

  • iPhone 本身有沒有 calling
  • iPhone.proto (Phone.prototype) 有沒有 calling
  • iPhone.proto.proto (Object.prototye) 有沒有 calling

是一層一層往上找的概念,這就是 prototype chain 原型鍊,那麼如果真的連 Object.prototype 也找不到呢,就會找到最頂層,回傳 null,那麼 iPhone.calling() 就會回傳錯誤。

由此可知,其實我們可以在 Object 上加上 prototype ,這樣 iPhone 一樣也可以使用 calling。

其實我們很常使用一些 method,像是 Array 的 push, join, .....這些,就是利用 prototype 的特性,當我們建立一個 array 的時候這個 array 的 proto 會與 JS中 Array.prototype 連接起來,所以我們才可以使用 Array.prototype 的 method。(正確來說是我們新增的 array 會繼承了 Array.prototype 的所有屬性和方法)

let arr = []
console.log(arr.__proto__ === Array.prototype) // true
console.log(arr.__proto__) // 會列出所有 Array 可以使用的 method

那麼 __proto__ 是怎麼來的呢,其實是因為 new 這個關鍵字在背後幫我們做了幾件事情,你可以把它想成這是以下這樣:

function Phone(name){
    return this.name = name
}

Phone.prototype.getName = function(){
    return this.name
}

Phone.prototype.calling = function(){
    console.log(this.name + ' => call someone...')
}

// new 的工作在這裡
function newPhone(name) {
    var obj = {}
    Phone.call(obj, name)
    obj.__proto__ = Phone.prototype
    return obj
}

// 這樣就可以不用 new 了
var oppo = newPhone('oppo')
oppo.calling()

new 的主要工作就是生成一個 Object,然後重點在將這個 Object 的 proto與 Phone 的 prototype 接起來,然後再回傳這個 Object,我們就可以使用 calling 了。

以上都是介紹 ES5 實現類別與實例的方式,那到了 ES6 之後,我們就有了 class 來取代這個 function ,但其實背後做的事情是差不多的。

class Phone{

    constructor(name) {
        this.name = name
    }

    getName(name) {
        return this.name
    }

    calling() {
        console.log(this.name + ' => call someone')
    }
}

var iPhone = new Phone('iPhone')
iPhone.calling()

注意這個 constructor,是使用了 class 自動幫我們創建的,裡面會放著我們對該實例的描述。

最後,在物件導向中有個很重要的觀念就是繼承,extends

假如我們現在新增一支手機,一樣有 calling 功能,也跟剛剛新增的手機一樣有名字,但這支手機可以照相其他手機不行,我們就可以使用繼承的概念實現,因為只是多了一個其他手機沒有的功能而已,剩下的都共用。

class Phone{

    constructor(name) {
        this.name = name
    }

    getName(name) {
        return this.name
    }

    calling() {
        console.log(this.name + ' => call someone')
    }
}

class PhoneM extends Phone {
    constructor(name) {
        super(name)
    }
    takePhoto(){
        console.log(this.name + ' => taking photo') 
    }
}


var iPhoneM = new PhoneM('iPhoneM')
iPhoneM.calling()

其實生活上很多例子都跟物件導向有相關,例如有個類別是 user,那就可以創建很多個 user 出來,每個 user 有不一樣的 id 但是都有著一樣的 method 可以使用,例如登入、新增文章....之類的,那我們可以使用繼承的觀念去新增一個 user 是 admin , 因為 admin 其實很多 method 和屬性都跟 user 共用,可能只是多加一個可以刪除文章的功能,當然生活上其實很多東西可以想成是類別與實例來看!這樣或許就不會那麼難理解了。


在以上我們大概知道了物件導向是什麼東西,也用了 JS 去簡單的示範怎麼呈現類別與實例,接下來我將這些片段整理一下,首先我們要知道設計物件導向的類別的時候幾個大觀念:

  • 抽象化 Abstraction:簡單的說,就是在抽象化概念中我們不需要去談細節,就像你在使用 setTimeout 或 監聽事件的時候,我們並不需要去知道運作的細節,我只要知道怎麼去使用,所以在設計類別的時候我們要站在使用者的角度,我只要讓使用者知道怎麼使用就好。
  • 封裝 Encapsulation:我們應該要明確定的定義哪些屬性是可以對外開放,哪些是不希望可以從外部被操控。
  • 繼承 Inheritance:就像剛剛舉的 user 與 admin 的例子,當我們建立 user 和 admin 類別時,實際上 admin 也是其中一個 user,所以 admin 應該要繼承 user 可以使用的屬性,並且 admin 有著自己獨有的屬性,總之,user 是 admin 的 parent 。
  • 多型 Polymrophism:可以想像成多型就是指一個類別可以再延伸出多個子類別,而此這些子類別有許多跟父類別共用的屬性,跟父類別有點相似但其實有一些不一樣。(其實這部分我還沒有到非常懂啦,先解說到這邊)

好了,現在對 JS 的物件導向有了基本認識之後,我們可以來認識 this 這個大魔王了


#js #物件導向







Related Posts

用 Nest.js 開發 API 吧 (二) - 專案架構

用 Nest.js 開發 API 吧 (二) - 專案架構

MTR04_0623

MTR04_0623

2. ECMAScript - Overview 概述

2. ECMAScript - Overview 概述


Comments