本文 AI 產出,尚未審核

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 會自動在背後執行以下三件事:
    1. 建立一個空物件,並把它的 [[Prototype]] 指向建構函式的 prototype
    2. this 綁定到那個新物件。
    3. 回傳新物件(除非建構函式明確回傳其他物件)。

原型(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 Userlet User = ... 會產生錯誤。 保持 命名唯一性,或將類別放在模組(module)內部。
過度使用 class 若僅需簡單的資料容器,使用 class 會增加不必要的複雜度。 依需求選擇 Object LiteralConstructor,避免過度抽象。

最佳實踐

  1. 根據需求選擇建立方式

    • 靜態、一次性資料 → Object Literal
    • 多個相似實例且需要共用行為 → Constructor + prototype
    • 需要繼承、靜態成員、私有欄位 → class
  2. 盡量使用 const 定義物件變數,避免意外重新指派。

    const settings = { debug: true }; // 只能改屬性,不能整體換成別的物件
    
  3. 使用 Object.freeze() 鎖定不應被修改的設定物件。

    const API_ENDPOINTS = Object.freeze({
      USERS: "/api/users",
      POSTS: "/api/posts",
    });
    
  4. 保持方法的單一職責,讓每個方法只負責一件事,提升可測試性。


實際應用場景

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 Literalclass 包裝。

// 假設從伺服器取得的資料
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 LiteralConstructor FunctionES6 Class 三個層面說明了不同的寫法與適用情境,並提供了實務範例、常見陷阱與最佳實踐。

  • Object Literal:快速、簡潔,適合一次性或靜態資料。
  • Constructor Function:適合大量相似實例,配合原型可共享方法。
  • Class:語法更直觀,支援繼承、私有欄位與靜態成員,適合較複雜的業務邏輯。

在實際開發時,根據需求選擇最合適的方式,同時遵守命名慣例、避免 this 失效、妥善管理原型共享資源,才能寫出乾淨、易讀且具彈性的程式碼。祝你在 JavaScript 的物件世界裡玩得開心,寫出更好的應用!