JavaScript 物件(Objects)- 建立物件(Object Literal / Constructor / Class)
簡介
在 JavaScript 中,物件是最核心的資料結構之一。幾乎所有的程式碼都會與物件互動,從瀏覽器提供的 DOM、API 回傳的 JSON,到自行設計的資料模型,都以物件的形式呈現。掌握如何建立物件,不僅能讓程式更具可讀性,也能提升維護性與擴充性。
本篇文章將從三種最常見的建立方式——物件直寫法(Object Literal)、建構函式(Constructor Function)、以及 ES6 Class——逐一說明其語法、使用情境與注意事項,並提供實務範例,協助初學者快速上手,同時給予中階開發者進一步的最佳實踐指引。
核心概念
1. 物件直寫法(Object Literal)
最簡單、最直觀的建立方式,就是直接以大括號 {} 定義屬性與方法。適合用於 一次性 或 靜態 的資料結構,例如設定檔、常數集合或簡單的資料傳遞。
// 例 1:設定檔
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retry: true,
};
// 例 2:簡易的「人」物件
const person = {
name: "陳小明",
age: 28,
// 方法寫法(ES5 風格)
greet: function () {
console.log(`哈囉,我是 ${this.name},今年 ${this.age} 歲。`);
},
};
// 呼叫方法
person.greet(); // => 哈囉,我是 陳小明,今年 28 歲。
重點提示
- 使用 冒號
:連接屬性名稱與值。 - 方法可以寫成
function或 ES6 簡寫greet() {}。 this代表當前物件本身,切勿忘記在方法內正確使用。
2. 建構函式(Constructor Function)
當需要 大量相似物件,或希望在建立時執行初始化程式碼,就可以使用建構函式。它本質上是一個普通函式,但慣例上會以大寫開頭,並以 new 關鍵字呼叫。
// 例 3:使用建構函式建立「車」物件
function Car(make, model, year) {
// 屬性
this.make = make;
this.model = model;
this.year = year;
// 方法
this.getInfo = function () {
return `${this.year} ${this.make} ${this.model}`;
};
}
// 使用 new 建立實例
const myCar = new Car("Toyota", "Corolla", 2022);
console.log(myCar.getInfo()); // => 2022 Toyota Corolla
為什麼要用 new?
new會自動在背後執行以下三件事:- 建立一個空物件,並把它的
[[Prototype]]指向建構函式的prototype。 - 把
this綁定到那個新物件。 - 回傳新物件(除非建構函式明確回傳其他物件)。
- 建立一個空物件,並把它的
原型(Prototype)
建構函式的 prototype 屬性可以放置所有實例共用的屬性或方法,避免每個實例都重複建立相同函式。
// 例 4:把共用方法放到 prototype
Car.prototype.start = function () {
console.log(`${this.make} ${this.model} 啟動!`);
};
myCar.start(); // => Toyota Corolla 啟動!
3. ES6 Class(類別)
ES6 引入了 class 語法,讓 物件導向程式設計 看起來更貼近其他語言(如 Java、C#)。實際上 class 只是建構函式與原型的語法糖,但可讀性更高,且支援 繼承(inheritance)、私有欄位 等新特性。
// 例 5:使用 class 定義「使用者」類別
class User {
// 建構子
constructor(username, email) {
this.username = username;
this.email = email;
}
// 公開方法
sayHello() {
console.log(`Hi, 我是 ${this.username}`);
}
// 靜態方法(不屬於實例)
static compare(u1, u2) {
return u1.username === u2.username;
}
}
// 建立實例
const alice = new User("alice", "alice@example.com");
alice.sayHello(); // => Hi, 我是 alice
// 呼叫靜態方法
const bob = new User("bob", "bob@example.com");
console.log(User.compare(alice, bob)); // => false
私有欄位(Private Fields)
從 ES2022 起,私有欄位以 # 開頭,只能在類別內部存取,提升封裝性。
class BankAccount {
#balance = 0; // 私有欄位
deposit(amount) {
if (amount > 0) this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const acc = new BankAccount();
acc.deposit(500);
console.log(acc.getBalance()); // => 500
// console.log(acc.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記使用 new |
直接呼叫建構函式會把 this 指向全域 (或 undefined in strict mode),導致屬性被掛在錯誤位置。 |
始終 使用 new,或在函式內加入 if (!(this instanceof Car)) return new Car(...); 以自動修正。 |
| 在原型上放置可變資料 | 若把陣列或物件放在 prototype,所有實例會共享同一個參考,導致意外的變更。 |
把可變資料放在建構子裡,或使用 Object.create(null) 建立獨立的物件。 |
this 失去綁定 |
把方法直接當成回呼傳給其他函式(如 setTimeout)時,this 會變成全域。 |
使用 箭頭函式、bind、或在方法內先 const self = this; 保存參考。 |
| 類別名稱與變數混用 | 在同一作用域內同時使用 class User 與 let User = ... 會產生錯誤。 |
保持 命名唯一性,或將類別放在模組(module)內部。 |
過度使用 class |
若僅需簡單的資料容器,使用 class 會增加不必要的複雜度。 |
依需求選擇 Object Literal 或 Constructor,避免過度抽象。 |
最佳實踐
根據需求選擇建立方式:
- 靜態、一次性資料 → Object Literal
- 多個相似實例且需要共用行為 → Constructor + prototype
- 需要繼承、靜態成員、私有欄位 → class
盡量使用
const定義物件變數,避免意外重新指派。const settings = { debug: true }; // 只能改屬性,不能整體換成別的物件使用
Object.freeze()鎖定不應被修改的設定物件。const API_ENDPOINTS = Object.freeze({ USERS: "/api/users", POSTS: "/api/posts", });保持方法的單一職責,讓每個方法只負責一件事,提升可測試性。
實際應用場景
1. 前端表單資料模型
在表單驗證時,我們常會把使用者輸入的資料封裝成一個「模型」物件,並提供驗證方法。
class LoginForm {
constructor(username = "", password = "") {
this.username = username;
this.password = password;
}
// 驗證規則
isValid() {
return this.username.trim() !== "" && this.password.length >= 6;
}
// 產生錯誤訊息
getError() {
if (this.username.trim() === "") return "使用者名稱不可為空";
if (this.password.length < 6) return "密碼至少 6 位元";
return null;
}
}
// 使用範例
const form = new LoginForm("alice", "12345");
if (!form.isValid()) console.warn(form.getError()); // => 密碼至少 6 位元
2. 圖形化資料結構(如樹、圖)
在遊戲或資料視覺化時,常需要自行實作節點(Node)物件。此時 class 的繼承與私有欄位特性非常有用。
class TreeNode {
#value;
constructor(value) {
this.#value = value;
this.children = [];
}
addChild(node) {
this.children.push(node);
}
getValue() {
return this.#value;
}
}
// 建立簡單的樹
const root = new TreeNode("root");
const childA = new TreeNode("A");
const childB = new TreeNode("B");
root.addChild(childA);
root.addChild(childB);
3. API 回傳的 JSON 轉換為物件
從後端取得的 JSON 常是純粹的資料,若要在前端加入行為(方法),可以先用 Object Literal 或 class 包裝。
// 假設從伺服器取得的資料
const raw = {
id: 101,
title: "學習 JavaScript",
createdAt: "2025-03-10T12:00:00Z",
};
// 包裝成具有格式化功能的物件
class Article {
constructor({ id, title, createdAt }) {
this.id = id;
this.title = title;
this.createdAt = new Date(createdAt);
}
// 方法:取得友善時間字串
getFormattedDate() {
return this.createdAt.toLocaleDateString("zh-TW");
}
}
const article = new Article(raw);
console.log(article.getFormattedDate()); // => 2025/3/10
總結
建立物件是 JavaScript 開發的基礎,也是構建可維護、可擴充程式的第一步。本文從 Object Literal、Constructor Function、ES6 Class 三個層面說明了不同的寫法與適用情境,並提供了實務範例、常見陷阱與最佳實踐。
- Object Literal:快速、簡潔,適合一次性或靜態資料。
- Constructor Function:適合大量相似實例,配合原型可共享方法。
- Class:語法更直觀,支援繼承、私有欄位與靜態成員,適合較複雜的業務邏輯。
在實際開發時,根據需求選擇最合適的方式,同時遵守命名慣例、避免 this 失效、妥善管理原型共享資源,才能寫出乾淨、易讀且具彈性的程式碼。祝你在 JavaScript 的物件世界裡玩得開心,寫出更好的應用!