Understand JavaScript #9 物件實體語法 (Object Literal Syntax)
本文主要內容為探討「物件」的相關知識,包含成員取用運算子、物件實體語法、偽裝命名空間,與 JSON 資料格式。
物件與「點」
- 成員取用運算子(點運算子)
- 計算成員取用運算子(中括號)
這邊的「成員」就是物件的成員,指物件內的屬性和方法。
物件在記憶體中有一個位址,它可以參考到其他與它相關的東西(屬性和方法)的記憶體位置。
取得位址的方式有兩種,使用「點」或「中括號」存取都可以,我自己是比較喜歡使用點存取,因為看起來比較簡潔、清楚、好寫,也比較容易除錯。
通常只有在無法使用點存取的時候,才會使用中括號。例如:要存取的屬性是動態字串,也就是一些可能會改變的字串。
1const person = new Object(); 2person['firstname'] = 'Grisia'; 3 4const lastnameProperty = 'lastname'; 5person[lastnameProperty] = 'Sun'; 6 7console.log(person); // {firstname: "Grisia", lastname: "Sun"} 8
題外話,其實
person.firstname
原本應該要寫成person.'firstname'
,但是因為我們有語法解析器,所以 JavaScript 自己會知道我們寫的firstname
要當字串處理。
當然屬性不只有字串,除了純值 (Primitive) 之外,也可以是一個物件。下方範例中,我們使用點運算子創造子物件(物件中的物件),然後增加屬性給子物件,並且賦予值。
使用中括號也可以,點與中括號雖然是不同的函式,不過做的事情是一樣的。
1person.address = new Object(); 2person.address.city = 'Tainan'; 3 4console.log(person.address.city); // Tainan 5console.log(person['address']['city']); // Tainan 6
上面因為運算子相同(優先性相同),所以要考慮相依性才能知道誰先運算。成員取用運算子是左相依性,所以會先在 person
中取得 address
屬性,之後再從 person.address
裡面找到 city
這個屬性。
物件與「物件實體」
物件實體語法 (Object Literal Syntax)
物件實體語法其實就是 new Object()
的 Shorthand。
它的功能非常強大,讓我們更快、更容易創造物件,不用一個一個增加屬性,程式碼寫法變得很簡潔易懂。
物件實體語法是用大括號去定義物件,並使用冒號來區隔名稱與值。
1const GrisiaSun = { 2 firstname: 'Grisia', 3 lastname: 'Sun', 4 address: { 5 street: '111 Main St.', 6 city: 'New York', 7 state: 'TW', 8 }, 9}; 10 11function greet(person) { 12 console.log(`Hi ${person.firstname}`); 13} 14 15greet(GrisiaSun); // Hi Grisia 16
透過物件實體語法,我們可以在任何地方創立物件,像是在物件被呼叫時,同時建立物件與它的屬性。
1greet({ 2 firstname: 'Mary', 3 lastname: 'Doe', 4}); // Hi Mary 5
與運算子在使用上的差異
使用「點運算子」直接創立以下物件與屬性,會出現錯誤訊息 "Uncaught TypeError: Cannot set property 'greet' of undefined"。
1const english = {}; 2english.greeting.greet = 'Hello!'; 3
為什麼會出現錯誤?我們一步一步檢查。
- 由於點運算子是左相依性,所以從最左邊開始執行
- 首先在
english
中尋找greeting
變數,因為是空物件,所以english.greeting
回傳undefined
- 接著傳入
undefined
給第二個點運算子,而 JavaScript 認定undefined
根本不是一個物件,所以告訴我們無法在undefined
裡面找到greet
這個屬性
所以我們必須多一個步驟,先在記憶體中創造一個物件,這樣就能再用點運算子連結 greet
屬性。
1const english = {}; 2english.greeting = {}; // 要先創造一個物件 3english.greeting.greet = 'Hello!'; 4
以上使用點運算子的寫法有點冗長,如果我們改用物件實體語法去初始化,就可以很快地建立完成。
1const english = { 2 greeting: { 3 greet: 'Hello!', 4 }, 5}; 6 7console.log(english.greeting.greet); // Hello! 8
其實對於 JavaScript 底層來說,使用「物件實體語法」或是「點運算子」建立物件都是一樣的,它們都是在建立物件與它的屬性和方法到記憶體中,因此對 JavaScript 而言是一樣的處理,但我們通常會選擇使用更強大、好寫的物件實體語法。
用物件「偽裝命名空間」
所謂的命名空間,大概就像是「容器」的概念,在不同命名空間可以有相同名稱的變數。
然而在 JavaScript 中並沒有命名空間,如果出現兩個相同名稱的變數,則新的會取代舊的,但是我們可以使用物件來偽造出命名空間。
以下範例就是藉由物件 english
和 spanish
作為容器,讓兩個可能是不同 JavaScript 檔案裡面的 greet
不會互相衝突、覆蓋。
1const english = {}; 2const spanish = {}; 3 4english.greet = 'Hello!'; 5spanish.greet = 'Hola!'; 6 7console.log(english); // {greet: "Hello!"} 8
透過物件實體語法,我們可以維持某一些程式碼與其他程式碼分離,這個做法在許多框架中都很常見,以下簡略模擬框架的程式碼寫法。
1const jQuery = { 2 greeting: { 3 greet: 'Hello!', 4 }, 5}; 6 7const vue = { 8 greeting: { 9 greet: 'Hola!', 10 }, 11}; 12
JavaScript Object Notation (JSON)
什麼是 JSON
JSON 是一個被「物件實體語法」啟發的格式,在 JSON 出現之前,網路資料的傳輸格式像是 XML 等都有一些額外、不必要的符號,像是一個屬性有前後兩個標籤,此時如果需要處理很多資料,這就會浪費很多下載頻寬。
JSON 長得跟物件實體語法很像,基本上就只差在 JSON 的屬性「一定」要被包在雙引號裡。
JSON 是有效的物件實體語法,因為物件實體語法的屬性是可以被包在引號裡的,因此 JSON 算是物件實體語法的其中一種,但並不是全部有效的物件實體語法都是 JSON 格式。
轉換與使用 JSON
- JSON.stringify(object)
- JSON.parse(string)
雖然 JSON 並不是 JavaScript 的一部份,但是 JavaScript 可以很簡單地解析它,只要透過 JavaScript 內建的功能就可以處理 JSON 資料。
JSON.stringify
會把物件轉換為 JSON String(JSON 語法的字串)。
1const objectLiteral = { 2 firstname: 'Damao', 3 isProgrammer: true, 4}; 5 6console.log(JSON.stringify(objectLiteral)); 7// {"firstname":"Damao","isProgrammer":true} 8 9console.log(typeof JSON.stringify(objectLiteral)); // string 10
JSON.parse
會接受一個符合 JSON 格式的字串,把它轉換為 JavaScript 物件。
1const jsonValue = '{"firstname":"Damao","isProgrammer":true}'; 2 3console.log(JSON.parse(jsonValue)); 4// {firstname: "Damao", isProgrammer: true} 5 6console.log(typeof JSON.parse(jsonString)); // object 7
回顧
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…
- 使用「點運算子」或「中括號運算子」存取物件
- JavaScript 的物件實體語法
- 用物件偽裝命名空間,避免命名衝突
- 物件實體語法與 JSON 字串的差別