JavaScript 基礎:物件操作與現代語法
各位好!
這篇是 JavaScript 系列的第六單元,我們要學習物件(Object)的操作。物件是 JavaScript 中最重要的資料結構,用於組織複雜的資料。
什麼是物件?
物件是鍵值對(key-value pairs)的集合,用於表示具有多個屬性的實體。
// 建立物件
const person = {
name: "小明",
age: 25,
city: "台北"
};
// 類比:物件就像一個有標籤的儲物櫃
// - name 標籤裡放著 "小明"
// - age 標籤裡放著 25
// - city 標籤裡放著 "台北"
建立物件的方式
物件字面值(最常用)
const user = {
name: "John",
age: 30,
email: "[email protected]"
};
// 空物件
const empty = {};
// 屬性名稱可以是字串
const obj = {
"first name": "John", // 有空格時必須用引號
"last-name": "Doe" // 有特殊字元時必須用引號
};
使用 new Object()
const person = new Object();
person.name = "小明";
person.age = 25;
// 不推薦,太冗長
使用建構函式
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = new Person("小明", 25);
存取屬性
點號表示法
const user = {
name: "Alice",
age: 25
};
// 讀取
console.log(user.name); // "Alice"
// 修改
user.age = 26;
// 新增
user.email = "[email protected]";
console.log(user);
// { name: "Alice", age: 26, email: "[email protected]" }
方括號表示法
const user = {
name: "Bob",
age: 30,
"favorite color": "blue"
};
// 讀取
console.log(user["name"]); // "Bob"
// 必須用方括號的情況
console.log(user["favorite color"]); // "blue"
// 動態屬性名稱
const key = "age";
console.log(user[key]); // 30
// 變數作為屬性名稱
const prop = "email";
user[prop] = "[email protected]";
檢查屬性
const user = {
name: "Charlie",
age: 28
};
// in 運算子
console.log("name" in user); // true
console.log("email" in user); // false
// hasOwnProperty 方法
console.log(user.hasOwnProperty("name")); // true
console.log(user.hasOwnProperty("email")); // false
// 直接存取(undefined 表示不存在)
console.log(user.email); // undefined
// 檢查是否存在且不為 undefined
if (user.email !== undefined) {
console.log("email 存在");
}
刪除屬性
const user = {
name: "David",
age: 32,
email: "[email protected]"
};
// 刪除屬性
delete user.email;
console.log(user); // { name: "David", age: 32 }
console.log(user.email); // undefined
物件方法
物件的屬性可以是函式,稱為「方法」。
const calculator = {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
}
};
console.log(calculator.add(5, 3)); // 8
console.log(calculator.subtract(5, 3)); // 2
// ES6 簡寫語法
const calculator2 = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
}
};
// 箭頭函式作為方法
const calculator3 = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
this 關鍵字
在物件方法中,this 指向該物件本身。
const person = {
firstName: "John",
lastName: "Doe",
fullName: function() {
return `${this.firstName} ${this.lastName}`;
},
greet: function() {
return `Hello, I'm ${this.fullName()}`;
}
};
console.log(person.fullName()); // "John Doe"
console.log(person.greet()); // "Hello, I'm John Doe"
// 注意:箭頭函式的 this 行為不同
const person2 = {
name: "Alice",
greet: () => {
// 箭頭函式沒有自己的 this
console.log(this); // 指向外層的 this(通常是 window 或 undefined)
}
};
// 建議:物件方法使用一般函式,不要用箭頭函式
物件解構(Destructuring)
從物件中提取屬性的簡潔語法。
const user = {
name: "Emma",
age: 27,
city: "New York",
country: "USA"
};
// 傳統寫法
const name = user.name;
const age = user.age;
// 解構語法
const { name, age } = user;
console.log(name); // "Emma"
console.log(age); // 27
// 提取多個屬性
const { city, country } = user;
console.log(city); // "New York"
console.log(country); // "USA"
// 重新命名
const { name: userName, age: userAge } = user;
console.log(userName); // "Emma"
console.log(userAge); // 27
// 預設值
const { email = "[email protected]" } = user;
console.log(email); // "[email protected]"
// 結合重新命名與預設值
const { phone: userPhone = "未提供" } = user;
console.log(userPhone); // "未提供"
// 剩餘屬性
const { name: n, ...rest } = user;
console.log(n); // "Emma"
console.log(rest); // { age: 27, city: "New York", country: "USA" }
函式參數解構
// 傳統寫法
function printUser(user) {
console.log(`姓名:${user.name}`);
console.log(`年齡:${user.age}`);
}
// 解構寫法
function printUser({ name, age }) {
console.log(`姓名:${name}`);
console.log(`年齡:${age}`);
}
const user = { name: "Frank", age: 35 };
printUser(user);
// 帶預設值
function createCard({ title, content = "無內容", author = "匿名" }) {
return `標題:${title}\n內容:${content}\n作者:${author}`;
}
console.log(createCard({ title: "測試" }));
// 標題:測試
// 內容:無內容
// 作者:匿名
屬性簡寫
當變數名稱與屬性名稱相同時,可以簡寫。
const name = "Grace";
const age = 29;
const city = "London";
// 傳統寫法
const user1 = {
name: name,
age: age,
city: city
};
// ES6 簡寫
const user2 = {
name,
age,
city
};
console.log(user2); // { name: "Grace", age: 29, city: "London" }
// 混合使用
const email = "[email protected]";
const user3 = {
name,
age,
email,
country: "UK" // 不同名稱照常寫
};
展開運算子(Spread Operator)
複製或合併物件。
const user = {
name: "Henry",
age: 31
};
// 複製物件(淺拷貝)
const userCopy = { ...user };
console.log(userCopy); // { name: "Henry", age: 31 }
// 修改複製的物件不影響原物件
userCopy.age = 32;
console.log(user.age); // 31(原物件未改變)
console.log(userCopy.age); // 32
// 合併物件
const address = {
city: "Paris",
country: "France"
};
const fullUser = { ...user, ...address };
console.log(fullUser);
// { name: "Henry", age: 31, city: "Paris", country: "France" }
// 覆蓋屬性
const updated = { ...user, age: 33 };
console.log(updated); // { name: "Henry", age: 33 }
// 新增屬性
const withEmail = { ...user, email: "[email protected]" };
console.log(withEmail);
// { name: "Henry", age: 31, email: "[email protected]" }
// 順序很重要
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
console.log({ ...obj1, ...obj2 }); // { a: 1, b: 3, c: 4 }
console.log({ ...obj2, ...obj1 }); // { a: 1, b: 2, c: 4 }
計算屬性名稱
使用變數或表達式作為屬性名稱。
const key = "email";
const value = "[email protected]";
const user = {
name: "Ivy",
[key]: value // 計算屬性名稱
};
console.log(user); // { name: "Ivy", email: "[email protected]" }
// 使用表達式
const prefix = "user";
const obj = {
[prefix + "Name"]: "Jack",
[prefix + "Age"]: 28
};
console.log(obj); // { userName: "Jack", userAge: 28 }
// 動態建立屬性
function createObject(key, value) {
return {
[key]: value
};
}
console.log(createObject("title", "Hello")); // { title: "Hello" }
遍歷物件
Object.keys()
const user = {
name: "Kate",
age: 26,
city: "Berlin"
};
// 取得所有鍵(key)
const keys = Object.keys(user);
console.log(keys); // ["name", "age", "city"]
// 遍歷
Object.keys(user).forEach(key => {
console.log(`${key}: ${user[key]}`);
});
// name: Kate
// age: 26
// city: Berlin
Object.values()
// 取得所有值(value)
const values = Object.values(user);
console.log(values); // ["Kate", 26, "Berlin"]
// 計算總和範例
const scores = { math: 85, english: 92, science: 88 };
const total = Object.values(scores).reduce((sum, score) => sum + score, 0);
console.log(total); // 265
Object.entries()
// 取得所有鍵值對(以陣列形式)
const entries = Object.entries(user);
console.log(entries);
// [["name", "Kate"], ["age", 26], ["city", "Berlin"]]
// 遍歷
Object.entries(user).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// 轉換物件
const doubled = Object.entries(scores)
.map(([subject, score]) => [subject, score * 2])
.reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
console.log(doubled);
// { math: 170, english: 184, science: 176 }
// 使用 Object.fromEntries()(ES2019)
const doubled2 = Object.fromEntries(
Object.entries(scores).map(([key, value]) => [key, value * 2])
);
console.log(doubled2);
for...in 迴圈
for (const key in user) {
console.log(`${key}: ${user[key]}`);
}
// 建議先檢查是否為自有屬性
for (const key in user) {
if (user.hasOwnProperty(key)) {
console.log(`${key}: ${user[key]}`);
}
}
其他實用方法
Object.assign()
// 複製物件
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original);
// 合併物件
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = Object.assign({}, obj1, obj2);
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }
// 覆蓋屬性
const obj3 = { a: 1, b: 2 };
const obj4 = { b: 3, c: 4 };
const result = Object.assign({}, obj3, obj4);
console.log(result); // { a: 1, b: 3, c: 4 }
// 注意:展開運算子更常用
const merged2 = { ...obj1, ...obj2 };
Object.freeze()
// 凍結物件,無法修改
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
};
Object.freeze(config);
config.apiUrl = "https://other-api.com"; // 無效
config.newProp = "test"; // 無效
console.log(config);
// { apiUrl: "https://api.example.com", timeout: 5000 }
// 檢查是否凍結
console.log(Object.isFrozen(config)); // true
Object.seal()
// 封閉物件,可以修改但無法新增或刪除屬性
const user = {
name: "Leo",
age: 30
};
Object.seal(user);
user.age = 31; // 可以修改
delete user.name; // 無法刪除
user.email = "test"; // 無法新增
console.log(user); // { name: "Leo", age: 31 }
實戰練習
練習 1:使用者資料管理
function createUser(name, age, email) {
return {
name,
age,
email,
isAdult() {
return this.age >= 18;
},
getInfo() {
return `${this.name} (${this.age} 歲) - ${this.email}`;
}
};
}
const user = createUser("Mike", 25, "[email protected]");
console.log(user.isAdult()); // true
console.log(user.getInfo()); // "Mike (25 歲) - [email protected]"
練習 2:購物車系統
const cart = {
items: [],
addItem(product, quantity = 1) {
const existing = this.items.find(item => item.product.id === product.id);
if (existing) {
existing.quantity += quantity;
} else {
this.items.push({ product, quantity });
}
},
removeItem(productId) {
this.items = this.items.filter(item => item.product.id !== productId);
},
getTotal() {
return this.items.reduce((total, item) => {
return total + (item.product.price * item.quantity);
}, 0);
},
clear() {
this.items = [];
}
};
const product1 = { id: 1, name: "商品 A", price: 100 };
const product2 = { id: 2, name: "商品 B", price: 200 };
cart.addItem(product1, 2);
cart.addItem(product2, 1);
console.log(cart.getTotal()); // 400
cart.removeItem(1);
console.log(cart.getTotal()); // 200
練習 3:設定檔管理
const defaultConfig = {
theme: "light",
language: "zh-TW",
notifications: true,
autoSave: false
};
function loadConfig(userConfig = {}) {
return {
...defaultConfig,
...userConfig
};
}
const config1 = loadConfig({ theme: "dark" });
console.log(config1);
// { theme: "dark", language: "zh-TW", notifications: true, autoSave: false }
const config2 = loadConfig({ language: "en", autoSave: true });
console.log(config2);
// { theme: "light", language: "en", notifications: true, autoSave: true }
練習 4:資料轉換
const users = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" },
{ id: 3, name: "Charlie", role: "admin" }
];
// 轉換成以 id 為 key 的物件
function arrayToObject(arr, key) {
return arr.reduce((obj, item) => {
obj[item[key]] = item;
return obj;
}, {});
}
const userMap = arrayToObject(users, "id");
console.log(userMap);
// {
// 1: { id: 1, name: "Alice", role: "admin" },
// 2: { id: 2, name: "Bob", role: "user" },
// 3: { id: 3, name: "Charlie", role: "admin" }
// }
// 按角色分組
function groupBy(arr, key) {
return arr.reduce((groups, item) => {
const group = item[key];
if (!groups[group]) {
groups[group] = [];
}
groups[group].push(item);
return groups;
}, {});
}
const byRole = groupBy(users, "role");
console.log(byRole);
// {
// admin: [
// { id: 1, name: "Alice", role: "admin" },
// { id: 3, name: "Charlie", role: "admin" }
// ],
// user: [
// { id: 2, name: "Bob", role: "user" }
// ]
// }
淺拷貝與深拷貝
淺拷貝
const original = {
name: "Nina",
scores: [85, 92, 88]
};
// 淺拷貝
const copy = { ...original };
// 修改第一層屬性不影響原物件
copy.name = "Oliver";
console.log(original.name); // "Nina"
// 但修改巢狀物件會影響原物件
copy.scores.push(95);
console.log(original.scores); // [85, 92, 88, 95](被影響了!)
深拷貝
// 方法一:JSON 序列化(簡單但有限制)
const deepCopy1 = JSON.parse(JSON.stringify(original));
// 限制:無法複製函式、undefined、Symbol
const obj = {
func: () => {},
undef: undefined,
date: new Date()
};
const copied = JSON.parse(JSON.stringify(obj));
console.log(copied); // { date: "2026-01-19T..." }(func 和 undef 不見了)
// 方法二:遞迴複製(完整版)
function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
// 方法三:使用 structuredClone()(現代瀏覽器)
const deepCopy2 = structuredClone(original);
小結
這篇我們學習了:
- 物件的建立與存取
- 物件方法與 this
- 物件解構與展開運算子
- 屬性簡寫與計算屬性名稱
- 遍歷物件的多種方法
- 實用的物件操作技巧
- 淺拷貝與深拷貝
下一步:
完成這篇後,你已經能夠: - 靈活操作物件資料 - 使用現代 JavaScript 語法 - 組織複雜的資料結構
前往下一篇:單元七:迴圈與迭代
在那裡,我們會學習如何重複執行程式碼。
0 留言
發表留言