National Yunlin University of Science and Technology
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

雲林科技大學 AQI 56 27°C PM2.5 10
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 08

物件

物件字面值、屬性存取、for…in 與參照
Interaction實作 20 minute(s)

Learning Objectives

  • 用物件字面值 { } 建立物件,說明屬性(鍵與值)與方法
  • 用點記法與中括號存取、新增、修改與刪除屬性,並分辨兩者的使用時機
  • 說明物件是參照:兩個變數可以指向同一個物件
  • 用 for…in、Object.keys、Object.values、Object.entries 走訪物件
  • 用解構取出屬性,用展開語法複製物件

物件:把相關的資料放在一起

要記錄一位同學的姓名、年齡、信箱,可以宣告三個變數;但同學一多,變數就散落一地,也不知道哪個信箱屬於誰。 物件(object)把描述同一件事物的資料放在一起:

const person = {
  name: '王小明',      // 屬性:鍵 name,值 '王小明'
  age: 20,
  speak() {             // 方法:值是函式的屬性
    return '你好';
  },
};
  • 每一筆「鍵: 值」叫做一個屬性(property),屬性之間用逗號分隔
  • 值可以是任何型別:字串、數字、陣列、另一個物件,或是函式
  • 值是函式的屬性叫做方法(method),代表這個物件「能做的事」

JavaScript 裡除了字串、數字、布林值、null、undefined 這些基本型別以外,其他的值都是物件: 陣列、函式、日期(Date)、正規表示法(RegExp)都是。你在 document.querySelector() 用到的點,就是在呼叫 document 物件的方法。

建立物件通常用上面的物件字面值 { };new Object() 也可以,但比較少用。 最後一個屬性後面可以加逗號(現在的瀏覽器都接受,加了之後新增屬性比較方便);很舊的瀏覽器會出錯,所以舊教材會提醒不要加。

存取、新增、修改與刪除屬性

右邊會畫出 person 物件。播放到第 15~17 行時,注意物件上多了 email、age 從 20 變成 21、favorite color 不見了。第 13 行的 person.key 為什麼是 undefined?

點記法與中括號記法

寫法 例子 什麼時候用
點記法(dot notation) person.name 屬性名稱是合法的識別字,而且寫程式時就知道名稱
中括號記法(bracket notation) person['name']、person[key] 名稱有空白或特殊字元,或名稱存在變數裡、執行時才知道

幾個要注意的地方:

  • 屬性的鍵一律是字串(或 Symbol)。obj[1] 和 obj['1'] 是同一個屬性。
  • 讀取不存在的屬性得到 undefined,不會出錯;但對 undefined 再取屬性(person.phone.number)就會出現 TypeError。
  • 'phone' in person 可以檢查屬性是否存在。
  • 物件裡可以再放物件,例如 person.address.city,這樣分層的做法也叫命名空間(namespace):把相關的名稱收在同一個物件底下。
  • 用 const 宣告的物件仍然可以修改屬性:const 只保證這個變數不會改指向別的物件,不保證物件的內容不變。

方法與 this

呼叫 counter.add(3) 時,右邊 add 的框架裡有一列 this,它指向的就是 counter。所以 this.count += n 改的是 counter.count。

物件是參照

這是物件最重要、也最容易出錯的觀念。

  • 基本型別(數字、字串、布林值…):let b = a 會把值複製一份給 b,之後改 b 不影響 a。
  • 物件:變數裡放的不是物件本身,而是「指向這個物件的參照」(reference)。const obj2 = obj1 只是讓 obj2 指向同一個物件, 沒有產生新物件。透過任何一個變數修改,另一個變數看到的都是改過的結果。

=== 比較兩個物件時,比的是「是不是同一個物件」,不是內容。兩個內容一模一樣、但分別建立的物件,=== 的結果是 false。

把物件傳給函式也一樣:函式拿到的是同一個物件,在函式裡修改屬性,外面也看得到。 要真的複製一份物件,請看後面的展開語法,以及第 10 課〈JSON 與複製〉。

兩個變數指向同一個物件

播放到第 9 行時看右邊:obj1 和 obj2 的箭頭指向同一個物件。第 13 行建立的 obj3 是另一個物件,雖然內容和 obj1 一樣。

想一想:屬性與參照

1.

下面的程式會印出什麼?

const car = { brand: 'Toyota', year: 2020 };
const prop = 'brand';
console.log(car[prop], car.prop);
2.

下面的程式會印出什麼?

const a = { n: 1 };
const b = a;
b.n = 2;
console.log(a.n);
3.

下面的程式會印出什麼?

const p = { x: 1 };
const q = { x: 1 };
console.log(p === q, p.x === q.x);
第 1 次作答

走訪物件:for…in 與 Object.keys/values/entries

寫法 得到
for (const key in obj) 每一圈拿到一個屬性名稱,要用 obj[key] 取值
Object.keys(obj) 所有屬性名稱組成的陣列
Object.values(obj) 所有屬性值組成的陣列
Object.entries(obj) [鍵, 值] 配對組成的陣列,常搭配 for…of 與解構

for…in 和 for…of 不一樣:

  • for…in 走訪屬性名稱(鍵),一般物件、陣列都可以用;用在陣列上拿到的是索引字串 '0'、'1'…
  • for…of 走訪值,只能用在可以逐一取值(iterable)的東西:陣列、字串、Map、Set…一般物件不行,會出現 TypeError: … is not iterable

for…in 還會走訪從原型(prototype)繼承來的可列舉屬性(原型在第 27 課說明)。 Object.keys 等方法只看物件自己的屬性,所以現在比較常用 Object.keys/entries 搭配 for…of。 需要用 for…in 又只想要自己的屬性時,舊程式會用 obj.hasOwnProperty(key) 過濾。

走訪陣列請用一般的 for 或 for…of,不要用 for…in(下一課〈陣列〉會細講)。

用 for…in 與 Object.entries 走訪成績

第 4 行的 subject 每一圈是一個屬性名稱。第 11 行的 Object.entries 把物件變成 [鍵, 值] 的陣列,每一圈直接解構成 subject 和 score 兩個變數。最後一段示範對一般物件用 for…of 會出錯。

解構與展開

解構(destructuring)一次把好幾個屬性取出來,存成同名的變數:

const { title, price } = book;            // 等於 const title = book.title; const price = book.price;
const { price: cost } = book;             // 取出 price,存到變數 cost
const { stock = 0 } = book;               // book 沒有 stock 時,用預設值 0

展開(spread)... 把一個物件的屬性一一複製到新的物件字面值裡:

const copy = { ...book };                 // 新物件,屬性和 book 一樣
const sale = { ...book, price: 360 };     // 先複製,再用後面的 price 蓋過去

展開建立的是新物件,所以 copy === book 是 false。但它只複製一層:如果屬性值本身是物件(例如 book.author), 新物件裡放的是同一個內層物件的參照。這叫淺複製,第 10 課會再比較淺複製與深複製。

解構與展開

看第 10 行執行完的右邊:sale 是新的物件,但 sale.author 和 book.author 的箭頭指向同一個 {name: 'Amy'}。

想一想:走訪、解構與展開

1.

下面的程式會印出什麼?

const user = { name: 'Amy', age: 20 };
const keys = [];
for (const k in user) {
  keys.push(k);
}
console.log(keys.join(','));
2.

下面的程式會印出什麼?

const item = { name: '筆', price: 30 };
const { name, qty = 1 } = item;
console.log(name, qty);
3.

下面的程式會印出什麼?

const a = { x: 1, tags: ['js'] };
const b = { ...a, x: 2 };
b.tags.push('css');
console.log(a.x, a.tags.length);
第 1 次作答

實作:購物車的總金額

cart 物件記錄每樣商品的價格。請走訪 cart(用 for…in 或 Object.entries 都可以),算出總金額,並找出最貴的商品名稱。

你的輸出
預期輸出

重點精華回顧

  • 物件把相關的資料放在一起:每一筆「鍵: 值」是一個屬性,值是函式的屬性叫做方法。
  • 存取屬性用點記法 obj.name 或中括號 obj['name'];名稱在變數裡或含有空白時用中括號。
  • 指定新的屬性就是新增,delete obj.key 刪除,'key' in obj 檢查是否存在;讀不存在的屬性得到 undefined。
  • 方法裡的 this 是呼叫它的那個物件(counter.add() 的 this 就是 counter)。
  • 物件是參照:const b = a 讓兩個變數指向同一個物件;=== 比的是不是同一個物件。
  • for…in 走訪屬性名稱;Object.keys/values/entries 把物件轉成陣列;一般物件不能用 for…of。
  • 解構 const { a, b } = obj 一次取出多個屬性;展開 { ...obj } 建立新物件,但只複製一層。
這一課完成了!
Next lesson:陣列
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。