JavaScript 基礎:物件操作與現代語法

2026-01-26 07:51 | By justin | JavaScript
(Updated: 2026-01-26 07:51)

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 留言

目前沒有留言

發表留言
回覆