JavaScript | 關於 Object ,一口氣全說完
前言
原諒我用醜字做開版圖,首先讓筆者先恭喜一下自己從教召地獄中脫離,距離上一次才經過了一年半,在工作才剛換的時候,這通知實在是來的有點快和無奈,不過這次在出發前隨手將「 Speaking JavaScript 」放進行李,想說重新讀一遍 JavaScript 中的 Object 知識,做下筆記,才讓這七天的精實(無聊)生活沒那麼無趣。
Object
本篇文章的內容都是從「 Speak JavaScript 」書中整理,會分成幾個部分講解:
- Object (物件)
- this(物件執行環境)
- Property attributes(特性屬性)
- Prototypal inheritance (原型繼承)
- Constructor (建構器)
- Constructor inheritance (建構器間的繼承)
那以上如果還有推薦多說明的部分,歡迎再留言告訴我!
Object 物件
在 JavaScript 中宣告一個 Object (物件)時使用首尾大括號建立範圍, Object 內的 Property (特性)為一個 key (鍵)搭配一個 value (值),並以逗號區隔每個 Property:
Property 的 value 可以是任何的型態值,包括 string 、 integer 、 function 又或是另一個 Object ,此外, Object 支援了在最後一個 Property 後加上逗號,即使它是最後,這個寫法可以讓之後要調整 Property 的時候比較不會出現錯誤,因為不必在意誰要擺在最後。
使用 Object 中的 Property 方式有兩種:
.
點號運算子,只要在 Object 名稱後使用.
並指定 Property 的 key ,即可讀取該特性的值。[]
中括號,在中括號裡可以是一個述句,會經由運算,並將值轉為字串後尋找對應的 key 值。
以下為兩種方式的使用範例:
.
點號運算子應該沒有問題,而下方 []
中刮號運算子中分別將 say
和 Hello
存到兩個變數,再在 []
內運算兩個變數加起來的值,得到 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 並不是建立在 mary 中,因此他之中的 this
就不會指向 Object 本身,而是代表全域的 Window
,更多關於 this
可以參考「淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂」,這篇文章講得非常清楚!
當遇到 this
指向錯誤,而無法取得 Object 內的 Property 時,有下列三種解法:
1.在使用 forEach
的情況下,它本身支援指定執行的 this
做為參數:
2.將 this
指定給另一個變數:
3.在執行 Function 時使用 call
、 apply
、 bind
指定 this
,以下先簡單介紹這三個的差別及用法:
使用 call
時,只需將要指定的 this
放入第一個參數,其餘從第二個開始傳入 Function 中,例如:
apply
的使用方式和 call
相同,只是在傳入要執行的 Function 需要的參數時,需將所有的參數放置於一個陣列中:
bind
與 call
傳入參數的方式相同,但 bind
並不會馬上執行 Function ,而是會回傳一個新的 Function :
稍微暸解 call
、 apply
、 bind
的使用方法後,便可以依照情況修改 Function ,解決上方抓不到 this
的問題:
經過 bind
指定 this
, Function 內就抓得到 mary 的 Property 了:
Property attributes 特性屬性
上方說明 Object 的時候有提到, Property 是由一個 key 及 value 定義的,但是嚴格來說, value 只是這個 Property key 的一個屬性而已,除了 value 外還有其他三個沒再露面的 Property attributes ,以下跟著 value 一同列出:
- value :代表此 Property 的值,如同上方的
Mary
及 Function 等,預設是undefined
。 - writable :代表此 Property 可否重新指定值,預設為
false
。 - enumerable :代表此 Property 的可列舉性,受到影響的會有
Object.keys()
及for(key in obj)
等,預設為false
。 - 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
確認使用上的差別:
以下為實際執行狀況:
另外在 enumerable
為 false
的狀態下,還是可以使用其他方式列出不可列舉的 Property :
1.使用 for(key in obj)
對剛剛的 mary
下迴圈,會把 enumerable
為 false
的資料給列出:
2.使用 Function Object.getOwnPropertyNames(obj)
,可以列出物件中所有「自有特性」:
最後,如果只是單純不想讓變數被改變為另一個 Object 的話,也能使用 ES6
提供的宣告語句 const
,雖然它無法阻止 Property 被改變或刪除,但是如果使用情況不需那麼嚴謹的話,還是可以考慮它:
Prototypal inheritance 原型繼承
一個物件裡除了上方稍微提到的「自有特性」外,還有由原型繼承而來的「繼承特性」,原型繼承的意思是當兩個物件間有相同或類似的 Function 時,可以將它取出放置 Prototype (原型)中,讓兩個物件在建立時以 Object.create(prototype)
繼承自該 Prototype ,便可以不必在建立物件時做多餘的定義:
上方的例子在建立 mary
及 kitty
時,都使用 Object.create
讓他們繼承自 personPrototype
,這時候就可以說 personPrototype
是 mary
及 kitty
的 Prototype。
那如果現在有三個 Object 分別為 a
、 b
、 c
, b
繼承自 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 上設定正確,如同下方操作:
執行結果:
Constructor 建構器
Constructor (建構器)是一個 Function ,可以利用它來建立一些相似的 Object ,被 Constructor 建立出來的 Object 被稱為它的 Instance (實體), Constructor 在使用時與一般 Function 不同,是藉由 new
來呼叫,也就是說,它只有再藉由 new
的狀態下使用才會是 Constructor 。
下方建立一個 Person
作為 Constructor ,並利用它建立 mary
及 kitty
物件:
在上方的例子中,就可以說 mary
和 kitty
是 Person
的 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 的人有所幫助。
如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!
參考文章