diff --git a/1-js/04-object-basics/01-object/2-hello-object/solution.md b/1-js/04-object-basics/01-object/2-hello-object/solution.md index 60083b963..293938cea 100644 --- a/1-js/04-object-basics/01-object/2-hello-object/solution.md +++ b/1-js/04-object-basics/01-object/2-hello-object/solution.md @@ -1,5 +1,4 @@ - ```js let user = {}; user.name = "John"; diff --git a/1-js/04-object-basics/01-object/2-hello-object/task.md b/1-js/04-object-basics/01-object/2-hello-object/task.md index 2841a058f..c9b732c81 100644 --- a/1-js/04-object-basics/01-object/2-hello-object/task.md +++ b/1-js/04-object-basics/01-object/2-hello-object/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# Hello, object +# 哈囉,物件 -Write the code, one line for each action: +寫段程式碼,每個動作各一行: -1. Create an empty object `user`. -2. Add the property `name` with the value `John`. -3. Add the property `surname` with the value `Smith`. -4. Change the value of the `name` to `Pete`. -5. Remove the property `name` from the object. +1. 建立一個空物件 `user`。 +2. 加入屬性 `name` 與值 `John`。 +3. 加入屬性 `surname` 與值 `Smith`。 +4. 改變 `name` 的值為 `Pete`。 +5. 從物件中移除屬性 `name`。 diff --git a/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js b/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js index db3283e49..67b255e9f 100644 --- a/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js +++ b/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js @@ -1,6 +1,6 @@ function isEmpty(obj) { for (let key in obj) { - // if the loop has started, there is a property + // 若迴圈開始,代表有屬性存在 return false; } return true; diff --git a/1-js/04-object-basics/01-object/3-is-empty/solution.md b/1-js/04-object-basics/01-object/3-is-empty/solution.md index b876973b5..31949f3b0 100644 --- a/1-js/04-object-basics/01-object/3-is-empty/solution.md +++ b/1-js/04-object-basics/01-object/3-is-empty/solution.md @@ -1 +1,2 @@ -Just loop over the object and `return false` immediately if there's at least one property. +只要遍歷該物件並在至少有一個屬性時 `return false` 就好。 + diff --git a/1-js/04-object-basics/01-object/3-is-empty/task.md b/1-js/04-object-basics/01-object/3-is-empty/task.md index c438d36a2..a5e58479b 100644 --- a/1-js/04-object-basics/01-object/3-is-empty/task.md +++ b/1-js/04-object-basics/01-object/3-is-empty/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Check for emptiness +# 檢查空物件 -Write the function `isEmpty(obj)` which returns `true` if the object has no properties, `false` otherwise. +寫一個函式 `isEmpty(obj)`,若物件沒有屬性時回傳 `true`,否則回傳 `false`。 -Should work like that: +應該要像這樣運作: ```js let schedule = {}; diff --git a/1-js/04-object-basics/01-object/4-const-object/solution.md b/1-js/04-object-basics/01-object/4-const-object/solution.md index f73c2f92b..9ce8afaa6 100644 --- a/1-js/04-object-basics/01-object/4-const-object/solution.md +++ b/1-js/04-object-basics/01-object/4-const-object/solution.md @@ -1,8 +1,8 @@ -Sure, it works, no problem. +當然可運作,沒問題。 -The `const` only protects the variable itself from changing. +`const` 只保護變數本身不被改變。 -In other words, `user` stores a reference to the object. And it can't be changed. But the content of the object can. +換句話說,`user` 儲存物件的參考,且其無法被改變。但物件的內容卻可以改變。 ```js run const user = { @@ -10,10 +10,11 @@ const user = { }; *!* -// works +// 可運作 user.name = "Pete"; */!* -// error +// 錯誤 user = 123; ``` + diff --git a/1-js/04-object-basics/01-object/4-const-object/task.md b/1-js/04-object-basics/01-object/4-const-object/task.md index a9aada631..531ba76a0 100644 --- a/1-js/04-object-basics/01-object/4-const-object/task.md +++ b/1-js/04-object-basics/01-object/4-const-object/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Constant objects? +# 常數物件? -Is it possible to change an object declared with `const`? What do you think? +有可能改變用 `const` 宣告的物件嗎?你怎麼想? ```js const user = { @@ -12,7 +12,8 @@ const user = { }; *!* -// does it work? +// 這能運作嗎? user.name = "Pete"; */!* ``` + diff --git a/1-js/04-object-basics/01-object/5-sum-object/task.md b/1-js/04-object-basics/01-object/5-sum-object/task.md index 7e3e048d0..2fb556f35 100644 --- a/1-js/04-object-basics/01-object/5-sum-object/task.md +++ b/1-js/04-object-basics/01-object/5-sum-object/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Sum object properties +# 加總物件屬性 -We have an object storing salaries of our team: +我們有個物件存放著我們團隊的薪資: ```js let salaries = { @@ -14,6 +14,7 @@ let salaries = { } ``` -Write the code to sum all salaries and store in the variable `sum`. Should be `390` in the example above. +寫一段程式碼加總薪資並儲存在變數 `sum` 內。上面的例子應該要是 `390`。 + +若 `salaries` 為空,則結果必須為 `0`。 -If `salaries` is empty, then the result must be `0`. \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/source.js b/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/source.js index a02b1e1cb..e6fdbf571 100644 --- a/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/source.js +++ b/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/source.js @@ -6,8 +6,8 @@ let menu = { function multiplyNumeric(obj) { - - /* your code */ + + /* 你的程式碼 */ } diff --git a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md index 33eb89220..fd350e546 100644 --- a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md +++ b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md @@ -2,14 +2,14 @@ importance: 3 --- -# Multiply numeric properties by 2 +# 數值屬性都乘以 2 -Create a function `multiplyNumeric(obj)` that multiplies all numeric properties of `obj` by `2`. +建立一個函式 `multiplyNumeric(obj)` 來把 `obj` 的所有數值屬性都乘以 2。 -For instance: +舉個例: ```js -// before the call +// 在呼叫前 let menu = { width: 200, height: 300, @@ -18,7 +18,7 @@ let menu = { multiplyNumeric(menu); -// after the call +// 在呼叫後 menu = { width: 400, height: 600, @@ -26,8 +26,7 @@ menu = { }; ``` -Please note that `multiplyNumeric` does not need to return anything. It should modify the object in-place. - -P.S. Use `typeof` to check for a number here. +請注意 `multiplyNumeric` 不需要回傳任何東西,它應該要原地(in place)修改物件。 +註:使用 `typeof` 來辨別數值類型。 diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 120e8dde0..f910df89d 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -1,60 +1,59 @@ +# 物件(Objects) -# Objects +如我們從 所知,JavaScript 內有七種資料類型。其中六種被稱為 "原生類型(primitive)",因為它們的值只包含了單一種東西(是個字串或數值或什麼的)。 -As we know from the chapter , there are seven data types in JavaScript. Six of them are called "primitive", because their values contain only a single thing (be it a string or a number or whatever). +相對的,物件被用來儲存使用鍵配對的多種資料群集與更為複雜的實體。在 JavaScript,物件幾乎滲入該語言的各個方面,所以我們必須在更深入其它主題前先理解物件。 -In contrast, objects are used to store keyed collections of various data and more complex entities. In JavaScript, objects penetrate almost every aspect of the language. So we must understand them first before going in-depth anywhere else. +物件可以經由花括號 `{…}` 與一些可選的 *屬性(properties)* 來建立。一個屬性也是一組 "鍵(key):值(value)" 配對,其中 `key` 是一串字串(也被稱為 "屬性名稱(property name)"),而 `value` 可以是任何東西。 -An object can be created with figure brackets `{…}` with an optional list of *properties*. A property is a "key: value" pair, where `key` is a string (also called a "property name"), and `value` can be anything. - -We can imagine an object as a cabinet with signed files. Every piece of data is stored in its file by the key. It's easy to find a file by its name or add/remove a file. +我們可以把物件想成一個存放簽名檔案的櫃子,每段資料經由鍵存放於對應的檔案中,要透過檔案的名稱尋找檔案或 增加/移除 檔案都很容易。 ![](object.svg) -An empty object ("empty cabinet") can be created using one of two syntaxes: +一個空物件("空櫃子")可以使用兩種語法的其中一種來建立: ```js -let user = new Object(); // "object constructor" syntax -let user = {}; // "object literal" syntax +let user = new Object(); // "物件建構子(object constructor)" 語法 +let user = {}; // "物件字面值(object literal)" 語法 ``` ![](object-user-empty.svg) -Usually, the figure brackets `{...}` are used. That declaration is called an *object literal*. +通常會使用花括號 `{...}`,這種宣告被稱為 *物件字面值(object literal)*。 -## Literals and properties +## 文字與屬性 -We can immediately put some properties into `{...}` as "key: value" pairs: +我們可以以 "鍵:值" 的方式立刻放入某些屬性到 `{...}` 內: ```js -let user = { // an object - name: "John", // by key "name" store value "John" - age: 30 // by key "age" store value 30 +let user = { // 一個物件 + name: "John", // 經由 "name" 鍵,存放值 "John" + age: 30 // 經由 "age" ,存放值 30 }; ``` -A property has a key (also known as "name" or "identifier") before the colon `":"` and a value to the right of it. +屬性有一個鍵(也被稱為 "名稱(name)" 或 "識別符(identifier)")於分號 `":"` 之前,並在其右側有著它的值。 -In the `user` object, there are two properties: +在 `user` 物件中,有兩個屬性: -1. The first property has the name `"name"` and the value `"John"`. -2. The second one has the name `"age"` and the value `30`. +1. 第一個屬性有著名稱 `"name"` 與值 `"John"`。 +2. 第二個有著名稱 `"age"` 與值 `30`。 -The resulting `user` object can be imagined as a cabinet with two signed files labeled "name" and "age". +產生的 `user` 物件可以被想成一個櫃子有著兩份被標為 "name" 與 "age" 的簽名檔案。 ![user object](object-user.svg) -We can add, remove and read files from it any time. +我們可以在任意時間增加、移除或讀取檔案。 -Property values are accessible using the dot notation: +屬性值可以使用點號(dot notation)來存取: ```js -// get property values of the object: +// 獲得物件的屬性值: alert( user.name ); // John alert( user.age ); // 30 ``` -The value can be of any type. Let's add a boolean one: +值可以是任意類型,來加一個布林看看: ```js user.isAdmin = true; @@ -62,7 +61,7 @@ user.isAdmin = true; ![user object 2](object-user-isadmin.svg) -To remove a property, we can use `delete` operator: +要移除一個屬性,我們可以用 `delete` 運算子: ```js delete user.age; @@ -70,40 +69,42 @@ delete user.age; ![user object 3](object-user-delete.svg) -We can also use multiword property names, but then they must be quoted: +我們也可以用多重詞彙作為屬性名稱,但它們必須置於引號內: ```js let user = { name: "John", age: 30, - "likes birds": true // multiword property name must be quoted + "likes birds": true // 多重詞彙屬性名稱必須置於引號內 }; ``` ![](object-user-props.svg) -The last property in the list may end with a comma: +列表中的最後一個屬性可以用逗號結尾: + ```js let user = { name: "John", age: 30*!*,*/!* } ``` -That is called a "trailing" or "hanging" comma. Makes it easier to add/remove/move around properties, because all lines become alike. -## Square brackets +這被稱為 "尾部逗號(trailing comma)" 或 "懸掛逗號(hanging comma)",可以使得 增加/移除/移動 屬性更為簡單,因為每一行都很相似。 -For multiword properties, the dot access doesn't work: +## 方括號 + +對於多重詞彙屬性,無法使用句點來存取: ```js run -// this would give a syntax error +// 這樣會語法錯誤 user.likes birds = true ``` -That's because the dot requires the key to be a valid variable identifier. That is: no spaces and other limitations. +這是因為句點需要鍵是個有效的變數識別符,也就是:沒有空格和其它某些限制。 -There's an alternative "square bracket notation" that works with any string: +作為替代,"方括號" 可用在任意字串上: ```js run let user = {}; @@ -118,20 +119,20 @@ alert(user["likes birds"]); // true delete user["likes birds"]; ``` -Now everything is fine. Please note that the string inside the brackets is properly quoted (any type of quotes will do). +至此所有東西都沒問題了,請注意括號內的字串要置於引號內(哪種引號都可以)。 -Square brackets also provide a way to obtain the property name as the result of any expression -- as opposed to a literal string -- like from a variable as follows: +方括號也提供能以表達式的結果來獲取屬性名稱的方法 -- 相對於文字字串 -- 像是下面這樣由變數獲取: ```js let key = "likes birds"; -// same as user["likes birds"] = true; +// 和 user["likes birds"] = true; 相同 user[key] = true; ``` -Here, the variable `key` may be calculated at run-time or depend on the user input. And then we use it to access the property. That gives us a great deal of flexibility. +在這裡,變數 `key` 可於執行期間被計算出來,或者經由使用者輸入得知。然後我們再使用它來存取屬性,這給了我們很大的彈性。 -For instance: +舉個例: ```js run let user = { @@ -141,11 +142,11 @@ let user = { let key = prompt("What do you want to know about the user?", "name"); -// access by variable -alert( user[key] ); // John (if enter "name") +// 經由變數存取 +alert( user[key] ); // John (若輸入 "name") ``` -The dot notation cannot be used in a similar way: +點號則不能用類似的方式: ```js run let user = { @@ -157,40 +158,41 @@ let key = "name"; alert( user.key ) // undefined ``` -### Computed properties +### 計算屬性(Computed properties) -We can use square brackets in an object literal. That's called *computed properties*. +我們可以在物件字面值使用方括號,這被稱為 *計算屬性(computed properties)*。 -For instance: +舉個例: ```js run let fruit = prompt("Which fruit to buy?", "apple"); let bag = { *!* - [fruit]: 5, // the name of the property is taken from the variable fruit + [fruit]: 5, // 屬性的名稱由變數 fruit 而來 */!* }; -alert( bag.apple ); // 5 if fruit="apple" +alert( bag.apple ); // 5,若 fruit="apple" ``` -The meaning of a computed property is simple: `[fruit]` means that the property name should be taken from `fruit`. +計算屬性的含義很簡單:`[fruit]` 代表著屬性名稱是由 `fruit` 而來。 + +所以,若訪問者輸入 `"apple"`,則 `bag` 就會變成 `{apple: 5}`。 -So, if a visitor enters `"apple"`, `bag` will become `{apple: 5}`. +實際上,這麼做也一樣: -Essentially, that works the same as: ```js run let fruit = prompt("Which fruit to buy?", "apple"); let bag = {}; -// take property name from the fruit variable +// 屬性名稱由 fruit 變數而來 bag[fruit] = 5; ``` -...But looks nicer. +...但看起來更讚。 -We can use more complex expressions inside square brackets: +我們可以在方括號內使用較為複雜的表達式: ```js let fruit = 'apple'; @@ -199,16 +201,14 @@ let bag = { }; ``` -Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are also more cumbersome to write. +方括號比點號更為強大,它允許任意屬性名稱和變數,但寫起來也較累贅。 -So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets. +所以大多時候,當屬性名稱已知且單純時,用句點就好,而若我們需要某些較複雜的東西時,再轉用方括號即可。 +````smart header="保留字可被允許用於屬性名稱" +變數不能使用語言保留字作為名稱,像是 "for"、"let"、"return" 等等。 - -````smart header="Reserved words are allowed as property names" -A variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc. - -But for an object property, there's no such restriction. Any name is fine: +但對於物件屬性而言,沒有這樣的限制,所有名稱都可以: ```js run let obj = { @@ -220,38 +220,37 @@ let obj = { alert( obj.for + obj.let + obj.return ); // 6 ``` -Basically, any name is allowed, but there's a special one: `"__proto__"` that gets special treatment for historical reasons. For instance, we can't set it to a non-object value: +基本上,任何名稱都是允許的,但只有一個特例:`__proto__`,這是因為歷史因素而被特別對待。舉個例,我們不能將它設為非物件的值: ```js run let obj = {}; obj.__proto__ = 5; -alert(obj.__proto__); // [object Object], didn't work as intended +alert(obj.__proto__); // [object Object],與預期的不同 ``` -As we see from the code, the assignment to a primitive `5` is ignored. +如同我們由程式碼看到的,對其指定原生值 `5` 被忽略了。 -That can become a source of bugs and even vulnerabilities if we intend to store arbitrary key-value pairs in an object, and allow a visitor to specify the keys. +若我們試圖在物件儲存任意鍵值配對,並允許訪問者指定其鍵,將會變成錯誤甚至漏洞的來源。 -In that case the visitor may choose `__proto__` as the key, and the assignment logic will be ruined (as shown above). +在這個例子中,訪問者想選擇 `__proto__` 作為鍵,但指定的邏輯就會失效(如上所顯示)。 -There is a way to make objects treat `__proto__` as a regular property, which we'll cover later, but first we need to know more about objects. +有個辦法可以使物件視 `__proto__` 為一般的屬性,這我們晚點會提到,但首先我們需要知道更多關於物件的內容。 -There's also another data structure [Map](info:map-set), that we'll learn in the chapter , which supports arbitrary keys. +也存在另一種資料結構 [Map](info:map-set),我們將於章節 中學到,能夠支援任意的鍵。 ```` +## 屬性值簡寫 -## Property value shorthand - -In real code we often use existing variables as values for property names. +在真正寫程式時我們常使用現存的變數作為屬性名稱的值。 -For instance: +舉個例: ```js run function makeUser(name, age) { return { name: name, age: age - // ...other properties + // ...其它屬性 }; } @@ -259,102 +258,102 @@ let user = makeUser("John", 30); alert(user.name); // John ``` -In the example above, properties have the same names as variables. The use-case of making a property from a variable is so common, that there's a special *property value shorthand* to make it shorter. +上面的例子中,屬性和變數有著一樣的名稱。這種從變數建立屬性的使用情境非常常見,因此有個特別的 *屬性值簡寫* 讓它更簡短。 -Instead of `name:name` we can just write `name`, like this: +我們可以只用 `name` 而非 `name:name`,像這樣: ```js function makeUser(name, age) { *!* return { - name, // same as name: name - age // same as age: age + name, // 和 name: name 相同 + age // 和 age: age 相同 // ... }; */!* } ``` -We can use both normal properties and shorthands in the same object: +我們可以在同一個物件內同時使用常規屬性和簡寫: ```js let user = { - name, // same as name:name + name, // 和 name:name 相同 age: 30 }; ``` -## Existence check +## 存在性確認 -A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined: +有個值得注意的物件功能是我們可以存取任意屬性,就算屬性不存在也不會有任何錯誤!存取一個不存在的屬性只會回傳 `undefined`,這提供了一個非常常見的方式來檢測屬性是否存在 -- 取得它並跟 undefined 做比較: ```js run let user = {}; -alert( user.noSuchProperty === undefined ); // true means "no such property" +alert( user.noSuchProperty === undefined ); // true 代表 "沒有這個屬性" ``` -There also exists a special operator `"in"` to check for the existence of a property. +同樣還有一個特殊的運算子 `"in"` 用來確認屬性是否存在。 + +語法是: -The syntax is: ```js "key" in object ``` -For instance: +舉個例: ```js run let user = { name: "John", age: 30 }; -alert( "age" in user ); // true, user.age exists -alert( "blabla" in user ); // false, user.blabla doesn't exist +alert( "age" in user ); // true,user.age 存在 +alert( "blabla" in user ); // false,user.blabla 不存在 ``` -Please note that on the left side of `in` there must be a *property name*. That's usually a quoted string. +請注意 `in` 的左側必須要是個 *屬性名稱*,通常是個置於引號內的字串。 -If we omit quotes, that would mean a variable containing the actual name will be tested. For instance: +若我們忽略引號,代表將使用某個變數包含的實際名稱來測試。舉個例: ```js run let user = { age: 30 }; let key = "age"; -alert( *!*key*/!* in user ); // true, takes the name from key and checks for such property +alert( *!*key*/!* in user ); // true,名稱由 key 而來,並檢查該屬性 ``` -````smart header="Using \"in\" for properties that store `undefined`" -Usually, the strict comparison `"=== undefined"` check the property existance just fine. But there's a special case when it fails, but `"in"` works correctly. +````smart header="對儲存 `undefined` 的屬性使用 \"in\"" +通常,使用嚴格比較 `"=== undefined"` 來確認屬性是否存在是沒問題的,然而有個特殊情況這麼做會失敗,但 `"in"` 能正確運作。 -It's when an object property exists, but stores `undefined`: +就是當物件屬性存在,卻儲存著 `undefined` 時: ```js run let obj = { test: undefined }; -alert( obj.test ); // it's undefined, so - no such property? +alert( obj.test ); // 這是 undefined,所以屬性不存在? -alert( "test" in obj ); // true, the property does exist! +alert( "test" in obj ); // true,該屬性存在! ``` +在上面的程式碼中,屬性 `obj.test` 技術上來說是存在的,所以 `in` 運算子運作正確。 -In the code above, the property `obj.test` technically exists. So the `in` operator works right. - -Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code. +類似的情況非常少發生,因為 `undefined` 通常不會被指定,我們對於 "未知" 或 "空白" 的值大多會使用 `null`,所以 `in` 運算子可以算是程式碼的過客。 ```` -## The "for..in" loop +## "for..in" 迴圈 -To walk over all keys of an object, there exists a special form of the loop: `for..in`. This is a completely different thing from the `for(;;)` construct that we studied before. +要走遍物件的所有鍵,有個特殊的迴圈型式可用: `for..in`。這是完全不同於我們曾經讀過的 `for(;;)` 的構造。 -The syntax: +語法: ```js for (key in object) { - // executes the body for each key among object properties + // 對每個物件中的 key 屬性執行程式碼本體 } ``` -For instance, let's output all properties of `user`: +舉個例,來輸出 `user` 的所有屬性: ```js run let user = { @@ -371,18 +370,17 @@ for (let key in user) { } ``` -Note that all "for" constructs allow us to declare the looping variable inside the loop, like `let key` here. +要注意所有 "for" 構造皆允許我們在迴圈內部宣告迴圈變數,像是這裡的 `let key`。 -Also, we could use another variable name here instead of `key`. For instance, `"for (let prop in obj)"` is also widely used. +同樣地,我們這裡可以使用另一個變數名稱而不用 `key`。例如,`"for (let prop in obj)"` 也很廣泛地使用。 +### 像物件一樣排序 -### Ordered like an object +物件是否有序?換句話說,若我們巡迴物件,是否所有屬性都能同樣以當初加入的順序取得呢?我們能依賴這性質嗎? -Are objects ordered? In other words, if we loop over an object, do we get all properties in the same order they were added? Can we rely on this? +簡短的答案是:"特異的順序":整數屬性為排序過的,其它則按照加入時的順序,細節在底下。 -The short answer is: "ordered in a special fashion": integer properties are sorted, others appear in creation order. The details follow. - -As an example, let's consider an object with the phone codes: +讓我們來考慮某個含有電話國碼的物件作為例子: ```js run let codes = { @@ -400,48 +398,48 @@ for (let code in codes) { */!* ``` -The object may be used to suggest a list of options to the user. If we're making a site mainly for German audience then we probably want `49` to be the first. +此物件被用來建議一個選項清單給使用者,假若我們正在建立以德國用戶為主的網站,那我們也許想要 `49` 為清單第一個選項。 -But if we run the code, we see a totally different picture: +但若我們執行程式,我們會看到完全不同的情景: -- USA (1) goes first -- then Switzerland (41) and so on. +- USA (1) 最先出現。 +- 然後是 Switzerland (41) 和其它等等。 -The phone codes go in the ascending sorted order, because they are integers. So we see `1, 41, 44, 49`. +因為電話國碼是整數,它們將會以升序出現。所以我們看到 `1, 41, 44, 49`。 -````smart header="Integer properties? What's that?" -The "integer property" term here means a string that can be converted to-and-from an integer without a change. +````smart header="整數屬性?那是什麼?" +這邊的 "整數屬性" 詞彙代表著某種字串,可以在不被變更的情況下轉換為整數。 -So, "49" is an integer property name, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not: +所以,"49" 是整數屬性名稱,因為當它被轉成整數值再轉回來時,依然保持一樣。但 "+49" 和 "1.2" 就不行: ```js run -// Math.trunc is a built-in function that removes the decimal part -alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property -alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property -alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property +// Math.trunc 是用來移除小數點部份的內建函式 +alert( String(Math.trunc(Number("49"))) ); // "49",一樣,是整數屬性 +alert( String(Math.trunc(Number("+49"))) ); // "49",跟 "+49" 不一樣 ⇒ 非整數屬性 +alert( String(Math.trunc(Number("1.2"))) ); // "1",跟 "1.2" 不一樣 ⇒ 非整數屬性 ``` ```` -...On the other hand, if the keys are non-integer, then they are listed in the creation order, for instance: +...另一方面,若鍵並非整數,則它們會以加入時的順序被列出,舉個例: ```js run let user = { name: "John", surname: "Smith" }; -user.age = 25; // add one more +user.age = 25; // 多加一個 *!* -// non-integer properties are listed in the creation order +// 非整數屬性為以加入時的順序被列出 */!* for (let prop in user) { alert( prop ); // name, surname, age } ``` -So, to fix the issue with the phone codes, we can "cheat" by making the codes non-integer. Adding a plus `"+"` sign before each code is enough. +所以,要修正電話國碼的問題,我們可以經由讓國碼變為非整數來 "作弊",在每個國碼之前加上一個正號 `"+"` 即可。 -Like this: +像這樣: ```js run let codes = { @@ -457,30 +455,30 @@ for (let code in codes) { } ``` -Now it works as intended. +現在它們會如預期般的運作了。 -## Copying by reference +## 依照參考複製(Copying by reference) -One of the fundamental differences of objects vs primitives is that they are stored and copied "by reference". +物件與原生類型之間有個本質上的差異,就是物件是 "依照參考(by reference)" 被儲存和複製。 -Primitive values: strings, numbers, booleans -- are assigned/copied "as a whole value". +原生值如:字串、數值、布林 -- 都是 "以完整的值" 被 指定/複製 的。 -For instance: +舉個例: ```js let message = "Hello!"; let phrase = message; ``` -As a result we have two independent variables, each one is storing the string `"Hello!"`. +結果是我們得到兩個完全獨立的變數,每個都存著字串 `"Hello!"`。 ![](variable-copy-value.svg) -Objects are not like that. +物件與這不同。 -**A variable stores not the object itself, but its "address in memory", in other words "a reference" to it.** +**變數儲存的不只物件本身,還有它的 "記憶體位址(address in memory)",換句話說就是對於它的 "參考(reference)"** -Here's the picture for the object: +這是關於物件的圖: ```js let user = { @@ -490,25 +488,25 @@ let user = { ![](variable-contains-reference.svg) -Here, the object is stored somewhere in memory. And the variable `user` has a "reference" to it. +這在裡,物件被儲存在記憶體某處,且變數 `user` 擁有一個對於它的 "參考"。 -**When an object variable is copied -- the reference is copied, the object is not duplicated.** +**當物件的變數被複製時 -- 只有參考被複製了,但物件並沒有被複製。** -If we imagine an object as a cabinet, then a variable is a key to it. Copying a variable duplicates the key, but not the cabinet itself. +若我們想像物件為一個櫃子,則變數就是它的鑰匙。複製變數時重鑄了另一把鑰匙,但並不是櫃子本身。 -For instance: +舉個例: ```js no-beautify let user = { name: "John" }; -let admin = user; // copy the reference +let admin = user; // 複製參考 ``` -Now we have two variables, each one with the reference to the same object: +現在我們有兩個變數,每個都參考至同樣的物件: ![](variable-copy-reference.svg) -We can use any variable to access the cabinet and modify its contents: +我們可以使用任意變數來存取櫃子並修改其內容: ```js run let user = { name: 'John' }; @@ -516,46 +514,46 @@ let user = { name: 'John' }; let admin = user; *!* -admin.name = 'Pete'; // changed by the "admin" reference +admin.name = 'Pete'; // 經由 "admin" 參考來更改 */!* -alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference +alert(*!*user.name*/!*); // 'Pete',從 "user" 的參考可以看到變動 ``` -The example above demonstrates that there is only one object. As if we had a cabinet with two keys and used one of them (`admin`) to get into it. Then, if we later use the other key (`user`) we would see changes. +上面的例子演示了此處只有一個物件存在。像是我們有一個櫃子與兩把鑰匙,在使用其中一把 (`admin`) 來存取後,我們晚點用另一把 (`user`) 時就可以看到變化。 -### Comparison by reference +### 依照參考比較(Comparison by reference) -The equality `==` and strict equality `===` operators for objects work exactly the same. +相等 `==` 和嚴格相等 `===` 運算子對物件來說運作起來都一樣。 -**Two objects are equal only if they are the same object.** +**兩個物件只有在它們是同一個物件時才會相等。** -For instance, if two variables reference the same object, they are equal: +舉個例,若兩個變數參考至同一個物件,它們會是相等的: ```js run let a = {}; -let b = a; // copy the reference +let b = a; // 複製參考 -alert( a == b ); // true, both variables reference the same object +alert( a == b ); // true,兩個變數參考至同一個物件 alert( a === b ); // true ``` -And here two independent objects are not equal, even though both are empty: +而這邊兩個獨立的物件就不相等,就算兩者皆為空也是: ```js run let a = {}; -let b = {}; // two independent objects +let b = {}; // 兩個獨立物件 alert( a == b ); // false ``` -For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are necessary very rarely and usually are a result of a coding mistake. +對於像是 `obj1 > obj2` 的比較或是與原生類型的比較 `obj == 5`,物件會被轉換為原生類型,我們很快會學習到物件轉換是如何運作的。但老實說,這種比較極少會需要,且通常是程式寫錯的結果。 -### Const object +### 常數物件 -An object declared as `const` *can* be changed. +物件被宣告為 `const` *可以* 被改變。 -For instance: +舉個例: ```js run const user = { @@ -569,9 +567,9 @@ user.age = 25; // (*) alert(user.age); // 25 ``` -It might seem that the line `(*)` would cause an error, but no, there's totally no problem. That's because `const` fixes only value of `user` itself. And here `user` stores the reference to the same object all the time. The line `(*)` goes *inside* the object, it doesn't reassign `user`. +看起來 `(*)` 這行會導致錯誤,但沒有,那完全不會有問題。這是因為 `const` 只固定了 `user` 本身的值,而這邊 `user` 始終都儲存了同一份物件的參考。`(*)` 行 *進入* 物件,它沒有重新指定 `user`。 -The `const` would give an error if we try to set `user` to something else, for instance: +若我們試著設定`user` 給其它東西時,`const` 將會產生錯誤,舉個例: ```js run const user = { @@ -579,26 +577,26 @@ const user = { }; *!* -// Error (can't reassign user) +// 錯誤(不能重新指定 user) */!* user = { name: "Pete" }; ``` -...But what if we want to make constant object properties? So that `user.age = 25` would give an error. That's possible too. We'll cover it in the chapter . +...但若我們想建立常數物件屬性怎麼辦?怎麼做才可以讓 `user.age = 25` 產生錯誤。這也是有可能的,我們將會在章節 中提到怎麼做。 -## Cloning and merging, Object.assign +## 複製與合併,Object.assign -So, copying an object variable creates one more reference to the same object. +所以,複製某物件變數會多建立一個對相同物件的參考。 -But what if we need to duplicate an object? Create an independent copy, a clone? +但若我們需要複製整個物件呢?怎麼建立獨立的複本,克隆體? -That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. Actually, that's rarely needed. Copying by reference is good most of the time. +這也是可行的,但稍微有點難度,因為 JavaScript 中沒有內建方法來做到這件事。事實上,很少需要這樣做,依照參考複製在大多時候就很好用了。 -But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level. +但若我們真的需要這麼做,那我們需要建立一個新的物件,並經由迭代現存物件的屬性並在原生類型層面上進行複製,才能複製其整體結構, -Like this: +像這樣: ```js run let user = { @@ -607,32 +605,33 @@ let user = { }; *!* -let clone = {}; // the new empty object +let clone = {}; // 新的空物件 -// let's copy all user properties into it +// 來對其複製整個 user 的屬性吧 for (let key in user) { clone[key] = user[key]; } */!* -// now clone is a fully independent clone -clone.name = "Pete"; // changed the data in it +// 現在 clone 是個完整的獨立克隆體了 +clone.name = "Pete"; // 改變它的資料 -alert( user.name ); // still John in the original object +alert( user.name ); // 原本的物件內依然是 John ``` -Also we can use the method [Object.assign](mdn:js/Object/assign) for that. +我們也可以使用 [Object.assign](mdn:js/Object/assign) 方法來處理。 -The syntax is: +語法是: ```js Object.assign(dest, [src1, src2, src3...]) ``` -- Arguments `dest`, and `src1, ..., srcN` (can be as many as needed) are objects. -- It copies the properties of all objects `src1, ..., srcN` into `dest`. In other words, properties of all arguments starting from the 2nd are copied into the 1st. Then it returns `dest`. +- 引數 `dest`,和 `src1, ..., srcN`(需要幾個都可以)皆為物件。 +- 它會複製所有 `src1, ..., srcN` 物件的屬性到 `dest` 中。換句話說,從第二個位置開始的所有引數的屬性將會被複製到第一個引數內,然後回傳 `dest`。 + +舉個例,我們可以使用它來合併多個物件成一個: -For instance, we can use it to merge several objects into one: ```js let user = { name: "John" }; @@ -640,25 +639,25 @@ let permissions1 = { canView: true }; let permissions2 = { canEdit: true }; *!* -// copies all properties from permissions1 and permissions2 into user +// 從 permissions1 和 permissions2 複製所有屬性到 user 中 Object.assign(user, permissions1, permissions2); */!* -// now user = { name: "John", canView: true, canEdit: true } +// 現在 user = { name: "John", canView: true, canEdit: true } ``` -If the receiving object (`user`) already has the same named property, it will be overwritten: +若接收的物件(`user`)已經有同樣名稱的屬性,則將會被覆蓋: ```js let user = { name: "John" }; -// overwrite name, add isAdmin +// 覆蓋 name,加入 isAdmin Object.assign(user, { name: "Pete", isAdmin: true }); -// now user = { name: "Pete", isAdmin: true } +// 現在 user = { name: "Pete", isAdmin: true } ``` -We also can use `Object.assign` to replace the loop for simple cloning: +我們也可以使用 `Object.assign` 來替換簡易複製之中的迴圈: ```js let user = { @@ -671,11 +670,12 @@ let clone = Object.assign({}, user); */!* ``` -It copies all properties of `user` into the empty object and returns it. Actually, the same as the loop, but shorter. +它複製 `user` 內的所有屬性到空物件中並回傳,事實上與迴圈相同,但更簡短。 -Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them? +至此我們只假設 `user` 的所有屬性皆為原生類型,但屬性也可以是其它物件的參考,這樣要怎麼做呢? + +像這樣: -Like this: ```js run let user = { name: "John", @@ -688,9 +688,10 @@ let user = { alert( user.sizes.height ); // 182 ``` -Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes: +現在要複製 `clone.sizes = user.sizes` 就有所不足了,因為 `user.sizes` 是一個物件,它將會依照參考來複製,因此 `clone` 和 `user` 將會共享同一個 sizes: + +像這樣: -Like this: ```js run let user = { name: "John", @@ -702,49 +703,48 @@ let user = { let clone = Object.assign({}, user); -alert( user.sizes === clone.sizes ); // true, same object +alert( user.sizes === clone.sizes ); // true,同一個物件 -// user and clone share sizes -user.sizes.width++; // change a property from one place -alert(clone.sizes.width); // 51, see the result from the other one +// user 和 clone 共享 sizes +user.sizes.width++; // 從某邊改變某個屬性 +alert(clone.sizes.width); // 51,將會從另一邊看到結果 ``` -To fix that, we should use the cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning". - -There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](http://w3c.github.io/html/infrastructure.html#safe-passing-of-structured-data). In order not to reinvent the wheel, we can use a working implementation of it from the JavaScript library [lodash](https://lodash.com), the method is called [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +要修正的話,我們應該使用複製迴圈來檢查每個 `user[key]` 的值,且若是個物件,則將它的架構也一起複製下來,這被稱為 "深層複製(deep cloning)"。 +已經有標準的演算法可以處理上述或更複雜情境中的深層複製,叫做 [Structured cloning algorithm](http://w3c.github.io/html/infrastructure.html#safe-passing-of-structured-data)。為了不要重新發明輪子,我們可以從 JavsScript 函式庫 [lodash](https://lodash.com) 中使用它的一個可行實作,其方法名為 [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep)。 +## 總結 -## Summary +物件具有些特殊特性的關聯矩陣。 -Objects are associative arrays with several special features. +它們儲存屬性(鍵值配對),其中: +- 屬性鍵必須為字串或符號(symbols)(通常為字串)。 +- 值可以為任意類型。 -They store properties (key-value pairs), where: -- Property keys must be strings or symbols (usually strings). -- Values can be of any type. +要存取屬性,我們可以使用: +- 點號:`obj.property`。 +- 方括號 `obj["property"]`。方括號允許鍵由某個變數而來,像是 `obj[varWithKey]`。 -To access a property, we can use: -- The dot notation: `obj.property`. -- Square brackets notation `obj["property"]`. Square brackets allow to take the key from a variable, like `obj[varWithKey]`. +額外操作: +- 要刪除屬性:`delete obj.prop`。 +- 要確認某個鍵的屬性是否存在:`"key" in obj`。 +- 要迭代整個物件:`for (let key in obj)` 迴圈。 -Additional operators: -- To delete a property: `delete obj.prop`. -- To check if a property with the given key exists: `"key" in obj`. -- To iterate over an object: `for (let key in obj)` loop. +物件經由參考被被指定與複製,換句話說,變數儲存的並非 "物件值",而是值的 "參考"(記憶體位址)。所以複製該變數或將它作為函式引數傳遞都只會複製參考,而非物件。所有經由複製的參考所做的操作(像是 新增/移除 屬性),都會在同一個物件上進行。 -Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object. All operations via copied references (like adding/removing properties) are performed on the same single object. +要建立一份 "真正的複本"(克隆體),我們可以使用 `Object.assign` 或 [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep)。 -To make a "real copy" (a clone) we can use `Object.assign` or [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +我們這章所學到的都被稱為 "普通物件(plain object)" 或就叫 `物件(Object)`。 -What we've studied in this chapter is called a "plain object", or just `Object`. +JavaScript 中還有更多其它種類的物件: -There are many other kinds of objects in JavaScript: +- `Array` 用來儲存有序的資料群集, +- `Date` 用來儲存日期與時間的資訊, +- `Error` 用來儲存關於錯誤的資訊。 +- ...等等。 -- `Array` to store ordered data collections, -- `Date` to store the information about the date and time, -- `Error` to store the information about an error. -- ...And so on. +它們有著自身的特殊特性,我們晚點會讀到。有時候人們說了像是 "矩陣類型" 或 "日期類型",但正式上這些都沒有自己的類型,而是屬於一個 "物件" 資料類型,且以多種方式對其延伸。 -They have their special features that we'll study later. Sometimes people say something like "Array type" or "Date type", but formally they are not types of their own, but belong to a single "object" data type. And they extend it in various ways. +JavaScript 中的物件非常強大,在這裡我們只粗略描述其超巨大主題的外貌。我們將會在教程接下來的部分內,密切使用物件並學習更多關於它們的知識。 -Objects in JavaScript are very powerful. Here we've just scratched the surface of a topic that is really huge. We'll be closely working with objects and learning more about them in further parts of the tutorial.