JavaScript | 關於 Object ,一口氣全說完

神Q超人
Enjoy life enjoy coding
13 min readMar 17, 2019

前言

原諒我用醜字做開版圖,首先讓筆者先恭喜一下自己從教召地獄中脫離,距離上一次才經過了一年半,在工作才剛換的時候,這通知實在是來的有點快和無奈,不過這次在出發前隨手將「 Speaking JavaScript 」放進行李,想說重新讀一遍 JavaScript 中的 Object 知識,做下筆記,才讓這七天的精實(無聊)生活沒那麼無趣。

Object

本篇文章的內容都是從「 Speak JavaScript 」書中整理,會分成幾個部分講解:

  1. Object (物件)
  2. this(物件執行環境)
  3. Property attributes(特性屬性)
  4. Prototypal inheritance (原型繼承)
  5. Constructor (建構器)
  6. Constructor inheritance (建構器間的繼承)

那以上如果還有推薦多說明的部分,歡迎再留言告訴我!

Object 物件

在 JavaScript 中宣告一個 Object (物件)時使用首尾大括號建立範圍, Object 內的 Property (特性)為一個 key (鍵)搭配一個 value (值),並以逗號區隔每個 Property:

Property 的 value 可以是任何的型態值,包括 string 、 integer 、 function 又或是另一個 Object ,此外, Object 支援了在最後一個 Property 後加上逗號,即使它是最後,這個寫法可以讓之後要調整 Property 的時候比較不會出現錯誤,因為不必在意誰要擺在最後。

使用 Object 中的 Property 方式有兩種:

  1. . 點號運算子,只要在 Object 名稱後使用 . 並指定 Property 的 key ,即可讀取該特性的值。
  2. [] 中括號,在中括號裡可以是一個述句,會經由運算,並將值轉為字串後尋找對應的 key 值。

以下為兩種方式的使用範例:

. 點號運算子應該沒有問題,而下方 [] 中刮號運算子中分別將 sayHello 存到兩個變數,再在 [] 內運算兩個變數加起來的值,得到 sayHello 這個 key 並取出 Function 值執行。

若要為 Object 異動或新增一個 property ,也是以 . 號運算子加上 Property 的 key ,在指定新值:

刪除的話可以使用 delete 加上 Object 的 Property 執行動作:

請注意!這與直接指定該 Property 的值為 undefined 不同, delete 會讓 Property 從 Object 中消失。

另外,如果要判斷一個變數是否為 Object 可以使用 typeof 方法:

typeof {} // "object"

但是要注意,除了真正的 Object 外, null 、 Array 也都會回傳 Object ,因此在判斷時,記得先處理掉變數型態可能是 null 或Array 的可能性。

this 執行環境

以 Object mary 來看,在 Object 中建立的 Function 都會個隱含的參數 this 可以使用,而 this 的值則指向 Object 本身,因此上方的 this.name 其實就等於 mary.name

不過這裡會有個隱藏的陷阱,當為 mary 這個物件加上印出所有 friend 的 Function :

雖然執行後能夠列出 Object 中 friends 的資料,但會發現在 forEach 內的 Function 無法用 this 得到 mary 的 name Property :

forEach 內的 Function 無法取得 Object 的 Property name

原因是在 forEach 內執行的 Function 並不是建立在 mary 中,因此他之中的 this 就不會指向 Object 本身,而是代表全域的 Window ,更多關於 this 可以參考「淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂」,這篇文章講得非常清楚!

當遇到 this 指向錯誤,而無法取得 Object 內的 Property 時,有下列三種解法:

1.在使用 forEach 的情況下,它本身支援指定執行的 this 做為參數:

為 forEach 內執行的 function 指定 this

2.將 this 指定給另一個變數:

3.在執行 Function 時使用 callapplybind 指定 this ,以下先簡單介紹這三個的差別及用法:

使用 call 時,只需將要指定的 this 放入第一個參數,其餘從第二個開始傳入 Function 中,例如:

apply 的使用方式和 call 相同,只是在傳入要執行的 Function 需要的參數時,需將所有的參數放置於一個陣列中:

bindcall 傳入參數的方式相同,但 bind 並不會馬上執行 Function ,而是會回傳一個新的 Function :

稍微暸解 callapplybind 的使用方法後,便可以依照情況修改 Function ,解決上方抓不到 this 的問題:

經過 bind 指定 this , Function 內就抓得到 mary 的 Property 了:

使用 bind 綁定 forEach 的 this

Property attributes 特性屬性

上方說明 Object 的時候有提到, Property 是由一個 key 及 value 定義的,但是嚴格來說, value 只是這個 Property key 的一個屬性而已,除了 value 外還有其他三個沒再露面的 Property attributes ,以下跟著 value 一同列出:

  1. value :代表此 Property 的值,如同上方的 Mary 及 Function 等,預設是 undefined
  2. writable :代表此 Property 可否重新指定值,預設為 false
  3. enumerable :代表此 Property 的可列舉性,受到影響的會有 Object.keys()for(key in obj) 等,預設為 false
  4. configurable : 代表此 Property 的可控制性,能夠讓物件的 Property 無法被刪除,預設為 false

將上方四個 attributes 寫成描述器的樣子:

{
value: '',
writable: false,
enumerable: false,
configurable: false,
}

使用 Object.defineProperty() 可以在定義一個新的 Property 時藉由描述器指定 Property attributes ,在定義 Property attributes 的時候,如果物件中有相同的 Property Key 那就只會去更新該 Property 的屬性,沒有才是定義一個新的:

定義完成後,當想確認某 Property 的 attributes 會需要 Object.getOwnPropertyDescriptor(Object,Property) ,這個 Function 會回傳該 Property 的 attributes 描述器:

下方透過將 value 以外的屬性都改為 false 確認使用上的差別:

以下為實際執行狀況:

當一個 Property 不可修改、列舉、控制時的操作狀態

另外在 enumerablefalse 的狀態下,還是可以使用其他方式列出不可列舉的 Property :

1.使用 for(key in obj) 對剛剛的 mary 下迴圈,會把 enumerablefalse 的資料給列出:

2.使用 Function Object.getOwnPropertyNames(obj) ,可以列出物件中所有「自有特性」:

最後,如果只是單純不想讓變數被改變為另一個 Object 的話,也能使用 ES6 提供的宣告語句 const ,雖然它無法阻止 Property 被改變或刪除,但是如果使用情況不需那麼嚴謹的話,還是可以考慮它:

Prototypal inheritance 原型繼承

一個物件裡除了上方稍微提到的「自有特性」外,還有由原型繼承而來的「繼承特性」,原型繼承的意思是當兩個物件間有相同或類似的 Function 時,可以將它取出放置 Prototype (原型)中,讓兩個物件在建立時以 Object.create(prototype) 繼承自該 Prototype ,便可以不必在建立物件時做多餘的定義:

上方的例子在建立 marykitty 時,都使用 Object.create 讓他們繼承自 personPrototype ,這時候就可以說 personPrototypemarykitty 的 Prototype。

那如果現在有三個 Object 分別為 abcb 繼承自 a ,而 c 又繼承自 b ,這種情況就可以說這三個 Object 在同一個原型關係上,也被稱為 Prototype chain (原型鏈)。

再接著說下去之前,先來提提在物件中呼叫 Property 的流程,當 obj.key 取出一個 Property 時,會先確認當前的 Object 是否擁有相同 key 的 Property ,如果沒有,便會由當前物件向繼承的 Object 中繼續尋找,一直到找到為止,而在找到該 Property 的那個 Object 也被稱為 Home Object。

既然知道在呼叫 Property 時會以 Home Object 為主,那當為某個 Object 新增一個與 Prototype 同名的 property 時,該 Object 便會成為呼叫 Property 的 Home Object ,當然這個操作也不會使 Prototype 中的同名 Property 受影響,只是它不是該 Property 的 Home Object 而已。

繼承後,也有 Function 可以檢查 mary 的 Prototype 是否為 personPrototype ,或是直接取得 kitty 的 Prototype 為何:

也可以透過 Object 的隱藏屬性 __proto__ 得到 Prototype ,經過繼承後的 Object 都會將原型放在 __proto__ 中,以 mary 為例:

提到這裡,已經可以知道一個 Object 會由 Property 及 Prototype 兩個重要的元素組成,因此當要複製另一個相同的 Object 時,就應該要把 Property 及 Prototype 都在新的 Object 上設定正確,如同下方操作:

執行結果:

新 Object 的 Property 和 Prototype 都和原本的相同

Constructor 建構器

Constructor (建構器)是一個 Function ,可以利用它來建立一些相似的 Object ,被 Constructor 建立出來的 Object 被稱為它的 Instance (實體), Constructor 在使用時與一般 Function 不同,是藉由 new 來呼叫,也就是說,它只有再藉由 new 的狀態下使用才會是 Constructor 。

下方建立一個 Person 作為 Constructor ,並利用它建立 marykitty 物件:

在上方的例子中,就可以說 marykittyPerson 的 Instance ,也可以發現 Constructor 能協助建立擁有基本資料的 Object ,而要處理這些 Object 的 Function 便能放在 Prototype 中,以達成資料在 Constructor 的 Instance , 方法在 Prototype 的關注點分離原則。

但是藉由 Constructor 建立的 Object 就不會再經過 Object.create ,該如何對那些 Instance 設定 Prototype 呢?答案在 Constructor 中,每一個 Constructor 都擁有 Prototype 的屬性,可以把方法設定在 Prototype 中,如此一來,只要是透過該 Constructor 建立的 Instance ,也都會擁有相同的 Prototype :

關於 Constructor 的 Prototype 也會有一個屬性為 constructor ,正常來說該屬性的值會指向 Constructor 自身,雖然在設計時,不會有非得使用該屬性的情況,但將它維持原有的樣子是個好習慣,因此在上方的例子中才沒有直接覆蓋 Prototype ,而是使用擴充的方式將 sayHello 定義到 Prototype 中,當然如果要直接覆蓋 Prototype ,也只需把 constructor 的值指向 Constructor 就行了:

Constructor inheritance 建構器間的繼承

Constructor 間的繼承有點類似「父類別」及「子類別」的關係,這裡要思考的是,如何在使用一個 Constructor 的時候去呼叫另一個 Constructor 建立 Property ,聽起來很複雜,但簡單來說,可以利用 call 在 Constructor 建立物件時將 this 指定給另一個 Constructor 設定 Property ,實作如下:

負責資料面的 Property 會比較好處理,但是方法類的 Prototype 該怎麼做呢?其實就和 Object 的繼承一樣,以父類別的 Prototype 為原型指定給子類別的 Prototype 就行了,但需要注意的是,因為直接指定父類別 Prototype 的關係,所以要重新把子類別的 constructor 屬性值指定給自己:

最後來了解一下 Super call (超呼叫),它的意思是在子類別的 Function 中,去呼叫父類別的 Function :

但從 Property 到 Prototype 為止的處理,在 Employee 中都是把 Person 給寫死,在實務上可以將 Person 存在 Employee 的 Property 中,維持更有彈性的寫法:

subclass 內做的事情和複製物件有點像,主要是在處理 Prototype 及 Property ,其實在 JavaScript 中,關於物件的使用及繼承,也只需搞懂這兩個屬性就行了!如上方,通過 subclass 便能將父子類別都設定好,透過將父類別的 Prototype 存在子類別的 Property 裡,在使用上也就更有彈性及簡單。

這篇文章從解召後就開始整理在軍中做的筆記撰寫,但是根據當兵定律,只要一回到家就一定會感冒,這兩天筆者貌似也得了流感,因此才拖了兩天才打完,希望這篇文章能對想了解 JavaScript 中 Object 的人有所幫助。

如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!

參考文章

  1. https://www.tenlong.com.tw/products/9789863478584

--

--