JS中class關鍵詞的使用解析

在JavaScript中,class是ES6(2015)引入的面向對象編程語法糖,核心作用是以更簡潔、語義化的方式定義構造函數和原型方法,替代了ES5中通過function+prototype模擬類的繁瑣寫法。class本質上是對原型鏈的封裝,並未改變JS基於原型的繼承本質,但讓類的定義、繼承、方法聲明更符合傳統面嚮對象語言的直覺,是現代JS開發中實現模塊化、可複用組件的核心工具。

一、class的基礎用法

1. 核心語法結構
// 定義類(類名首字母大寫,規範)
class ClassName {
  // 構造函數:實例化時執行,初始化實例屬性
  constructor(參數1, 參數2) {
    this.屬性1 = 參數1;
    this.屬性2 = 參數2;
  }

  // 實例方法(掛載到原型鏈)
  方法名1() {
    // 邏輯代碼
  }

  // 靜態方法(掛載到類本身,通過類名調用)
  static 方法名2() {
    // 邏輯代碼
  }

  // 訪問器屬性(get/set)
  get 屬性名() {
    return this.xxx;
  }
  set 屬性名(值) {
    this.xxx = 值;
  }
}

// 實例化類(必須用new)
const 實例 = new ClassName(值1, 值2);

核心規則:

  • class定義的類**必須用****new**實例化,直接調用會報錯(區別於ES5構造函數);
  • constructor是類的默認方法,實例化時自動執行,若無顯式定義,會默認生成空構造函數;
  • 類內部的方法默認是不可枚舉的(區別於ES5手動掛載到prototype的方法)。
2. 基礎示例:定義簡單類
// 定義Person類
class Person {
  // 構造函數:初始化姓名和年齡
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 實例方法:打招呼
  sayHello() {
    console.log(`你好,我是${this.name},今年${this.age}歲`);
  }

  // 靜態方法:類的工具方法
  static isAdult(age) {
    return age >= 18;
  }

  // 訪問器:獲取年齡描述
  get ageDesc() {
    return this.age >= 18 ? "成年人" : "未成年人";
  }

  // 訪問器:修改年齡(做校驗)
  set ageDesc(newDesc) {
    console.warn("年齡描述無法直接修改,請修改age屬性");
  }
}

// 實例化
const person1 = new Person("張三", 20);
// 調用實例方法
person1.sayHello(); // 輸出:你好,我是張三,今年20歲
// 訪問getter屬性(無需加括號)
console.log(person1.ageDesc); // 輸出:成年人
// 調用靜態方法(通過類名)
console.log(Person.isAdult(17)); // 輸出:false

// 錯誤用法:直接調用類(無new)
// Person("李四", 25); // 報錯:Uncaught TypeError: Class constructor Person cannot be invoked without 'new'

二、class的核心特性

1. 實例方法與靜態方法
  • 實例方法:掛載到類.prototype,只能通過實例調用,方法內this指向實例;
  • 靜態方法:用static修飾,掛載到類本身,通過類名調用,方法內this指向類(而非實例)。
class MathUtil {
  // 實例方法:計算平方(無意義,僅示例)
  square(num) {
    return num * num;
  }

  // 靜態方法:計算求和(工具方法,適合靜態)
  static sum(a, b) {
    return a + b;
  }
}

const util = new MathUtil();
// 實例方法調用
console.log(util.square(5)); // 輸出:25
// 靜態方法調用(類名.方法)
console.log(MathUtil.sum(3, 4)); // 輸出:7
// 錯誤:實例無法調用靜態方法
// console.log(util.sum(1,2)); // 輸出:undefined
2. 類的繼承(extends + super)

ES6通過extends實現類的繼承,super用於調用父類的構造函數或方法,是實現繼承的核心。

// 父類:Person
class Person {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(`姓名:${this.name}`);
  }
}

// 子類:Student(繼承Person)
class Student extends Person {
  constructor(name, studentId) {
    // 必須先調用super,才能使用this(子類構造函數規則)
    super(name); // 調用父類構造函數,初始化name
    this.studentId = studentId; // 子類自有屬性
  }

  // 子類實例方法
  sayId() {
    console.log(`學號:${this.studentId}`);
  }

  // 重寫父類方法
  sayName() {
    super.sayName(); // 調用父類的sayName
    console.log(`(學生)姓名:${this.name}`);
  }
}

// 實例化子類
const student = new Student("李四", "S1001");
student.sayName();
// 輸出:
// 姓名:李四
// (學生)姓名:李四
student.sayId(); // 輸出:學號:S1001

關鍵規則:

  • 子類構造函數中,必須先調用**super()**,才能使用this(否則報錯);
  • super既可以調用父類構造函數(super(參數)),也可以調用父類方法(super.方法名());
  • 子類會繼承父類的實例方法、靜態方法(包括static方法)。
3. 私有屬性/方法(ES2022)

ES2022引入#前綴定義類的私有成員,私有屬性/方法僅能在類內部訪問,外部無法讀取/修改,解決了ES5中“偽私有屬性”(如_name)可被外部修改的問題。

class User {
  // 公有屬性
  publicId = 1001;
  // 私有屬性(#前綴)
  #password = "123456";

  // 實例方法:訪問私有屬性
  checkPassword(input) {
    return input === this.#password;
  }

  // 私有方法(#前綴)
  #encrypt(str) {
    return str + "_encrypted";
  }

  getEncryptedPwd() {
    return this.#encrypt(this.#password);
  }
}

const user = new User();
// 訪問公有屬性
console.log(user.publicId); // 輸出:1001
// 調用方法訪問私有屬性
console.log(user.checkPassword("123456")); // 輸出:true
console.log(user.getEncryptedPwd()); // 輸出:123456_encrypted

// 錯誤:外部訪問私有屬性/方法(直接報錯)
// console.log(user.#password); // 報錯:Uncaught SyntaxError: Private field '#password' must be declared in an enclosing class
// console.log(user.#encrypt("test")); // 報錯

三、class的實際應用場景

1. 封裝可複用組件(前端/Node.js)

在前端框架(React/Vue)或Node.js開發中,用class封裝組件/工具類,實現代碼複用和模塊化。

// 前端示例:封裝彈窗組件類
class Modal {
  constructor(title, content) {
    this.title = title;
    this.content = content;
    this.dom = null; // 存儲彈窗DOM元素
  }

  // 渲染彈窗
  render() {
    this.dom = document.createElement("div");
    this.dom.innerHTML = `
      <div class="modal-title">${this.title}</div>
      <div class="modal-content">${this.content}</div>
      <button class="modal-close">關閉</button>
    `;
    document.body.appendChild(this.dom);
    // 綁定關閉事件
    this.dom.querySelector(".modal-close").addEventListener("click", () => {
      this.close();
    });
  }

  // 關閉彈窗
  close() {
    this.dom?.remove();
  }

  // 靜態方法:快速創建提示彈窗
  static alert(content) {
    const modal = new Modal("提示", content);
    modal.render();
    // 3秒後自動關閉
    setTimeout(() => modal.close(), 3000);
  }
}

// 使用:創建自定義彈窗
const customModal = new Modal("自定義標題", "自定義內容");
customModal.render();

// 使用:快速提示彈窗
Modal.alert("操作成功!");
2. 實現單例模式

通過static方法和私有屬性,可輕鬆實現類的單例模式(確保類只有一個實例)。

class Singleton {
  // 私有屬性:存儲唯一實例
  static #instance = null;

  constructor(name) {
    this.name = name;
  }

  // 靜態方法:獲取唯一實例
  static getInstance(name) {
    if (!this.#instance) {
      this.#instance = new Singleton(name);
    }
    return this.#instance;
  }
}

// 獲取實例1
const instance1 = Singleton.getInstance("實例1");
// 獲取實例2(複用實例1)
const instance2 = Singleton.getInstance("實例2");

console.log(instance1 === instance2); // 輸出:true
console.log(instance1.name); // 輸出:實例1(僅第一次初始化生效)
3. 結合接口請求封裝服務類(前端)

在前端項目中,用class封裝API服務類,統一管理接口請求邏輯,便於維護和擴展。

class UserService {
  // 基礎URL
  static baseUrl = "https://api.example.com/user";

  // 獲取用户信息
  async getUserInfo(userId) {
    try {
      const res = await fetch(`${UserService.baseUrl}/${userId}`);
      if (!res.ok) throw new Error(`請求失敗:${res.status}`);
      return res.json();
    } catch (error) {
      console.error("獲取用户信息失敗:", error);
      throw error;
    }
  }

  // 更新用户信息
  async updateUserInfo(userId, data) {
    try {
      const res = await fetch(`${UserService.baseUrl}/${userId}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(data)
      });
      if (!res.ok) throw new Error(`更新失敗:${res.status}`);
      return res.json();
    } catch (error) {
      console.error("更新用户信息失敗:", error);
      throw error;
    }
  }
}

// 使用服務類
const userService = new UserService();
userService.getUserInfo(1001)
  .then(data => console.log("用户信息:", data))
  .catch(err => console.log("處理錯誤:", err));

四、class的注意事項

1. 類的提升(hoisting)

class聲明存在“提升”但未初始化,無法在聲明前調用(區別於函數聲明),屬於“暫時性死區”。

// 錯誤:類聲明前實例化
// const p = new Person(); // 報錯:Uncaught ReferenceError: Cannot access 'Person' before initialization

class Person {}
// 正確:聲明後實例化
const p = new Person();
2. 類方法無原型(不可用new調用)

類的實例方法是普通函數,但沒有prototype屬性,無法用new調用(區別於ES5構造函數)。

class Test {
  fn() {}
}

const t = new Test();
// 錯誤:實例方法無法new調用
// new t.fn(); // 報錯:Uncaught TypeError: t.fn is not a constructor
3. 繼承內置類(Array/Map等)

class可繼承JS內置類(如Array),擴展其功能,實現自定義數據結構。

// 自定義數組類:擴展求和方法
class MyArray extends Array {
  sum() {
    return this.reduce((total, num) => total + num, 0);
  }
}

const arr = new MyArray(1, 2, 3, 4);
console.log(arr.sum()); // 輸出:10
console.log(arr instanceof Array); // 輸出:true(繼承Array)
4. 避免過度封裝

class適合封裝有狀態、有繼承關係的邏輯,若僅需簡單的工具函數集合,優先使用對象字面量或模塊導出,避免過度封裝增加複雜度。

總結

  1. class是ES6的語法糖,本質基於原型鏈,核心用於定義類、實現繼承,讓面向對象代碼更簡潔語義化;
  2. 核心語法包括constructor(初始化)、實例方法、靜態方法(static)、訪問器(get/set)、繼承(extends+super);
  3. ES2022的#前綴可定義私有屬性/方法,解決了傳統原型模式的私有成員問題;
  4. 適用場景:封裝可複用組件、實現單例模式、管理接口服務等,是現代JS模塊化開發的核心工具。