1. Class 基本语法

基础定义
// ES6 Class 语法
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // 实例方法
  sayHello() {
    console.log(`Hello, I'm ${this.name}`);
  }
  
  // 静态方法
  static getSpecies() {
    return 'Homo sapiens';
  }
  
  // Getter
  get info() {
    return `${this.name}, ${this.age} years old`;
  }
  
  // Setter
  set nickname(value) {
    this._nickname = value;
  }
}

// 使用
const person = new Person('Alice', 25);
person.sayHello(); // "Hello, I'm Alice"
console.log(Person.getSpecies()); // "Homo sapiens"
console.log(person.info); // "Alice, 25 years old"

2. Class 与构造函数的对比

构造函数方式(ES5)
function PersonES5(name, age) {
  this.name = name;
  this.age = age;
}

PersonES5.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

PersonES5.getSpecies = function() {
  return 'Homo sapiens';
};
Class 方式(ES6)
class PersonES6 {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  sayHello() {
    console.log(`Hello, I'm ${this.name}`);
  }
  
  static getSpecies() {
    return 'Homo sapiens';
  }
}

3. Class 的重要特性

严格模式
class MyClass {
  // Class 内部默认启用严格模式
  method() {
    // this 在严格模式下未绑定时为 undefined
  }
}
必须使用 new 调用
class MyClass {}
// const obj = MyClass(); // TypeError: Class constructor MyClass cannot be invoked without 'new'
const obj = new MyClass(); // 正确
不可枚举的方法
class Test {
  method() {}
}

console.log(Object.keys(Test.prototype)); // []
console.log(Object.getOwnPropertyNames(Test.prototype)); // ['constructor', 'method']

4. 继承机制

基本继承
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类构造函数
    this.breed = breed;
  }
  
  speak() {
    super.speak(); // 调用父类方法
    console.log(`${this.name} barks`);
  }
  
  getInfo() {
    return `${this.name} is a ${this.breed}`;
  }
}

const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak();
// "Buddy makes a sound"
// "Buddy barks"
继承内置对象
class MyArray extends Array {
  constructor(...args) {
    super(...args);
  }
  
  first() {
    return this[0];
  }
  
  last() {
    return this[this.length - 1];
  }
}

const arr = new MyArray(1, 2, 3);
console.log(arr.first()); // 1
console.log(arr.last());  // 3

5. 静态方法和属性

静态方法
class MathUtils {
  static add(a, b) {
    return a + b;
  }
  
  static multiply(a, b) {
    return a * b;
  }
}

console.log(MathUtils.add(2, 3)); // 5
静态属性(ES2022)
class MyClass {
  static version = '1.0.0';
  
  static get DEFAULT_CONFIG() {
    return { debug: false };
  }
}

console.log(MyClass.version); // "1.0.0"
console.log(MyClass.DEFAULT_CONFIG); // { debug: false }

6. Getter 和 Setter

class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  
  set fullName(value) {
    const parts = value.split(' ');
    this.firstName = parts[0];
    this.lastName = parts[1];
  }
  
  get age() {
    return this._age || 0;
  }
  
  set age(value) {
    if (value < 0) {
      throw new Error('Age cannot be negative');
    }
    this._age = value;
  }
}

const user = new User('John', 'Doe');
console.log(user.fullName); // "John Doe"

user.fullName = 'Jane Smith';
console.log(user.firstName); // "Jane"
console.log(user.lastName);  // "Smith"

7. 私有属性和方法(ES2022)

class BankAccount {
  // 私有属性
  #balance = 0;
  #accountNumber;
  
  constructor(accountNumber, initialBalance = 0) {
    this.#accountNumber = accountNumber;
    this.#balance = initialBalance;
  }
  
  // 公共方法
  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    }
  }
  
  withdraw(amount) {
    if (amount > 0 && amount <= this.#balance) {
      this.#balance -= amount;
    }
  }
  
  getBalance() {
    return this.#balance;
  }
  
  // 私有方法
  #validateAmount(amount) {
    return typeof amount === 'number' && amount > 0;
  }
  
  // 私有 getter/setter
  get #maskedAccountNumber() {
    return `****${this.#accountNumber.slice(-4)}`;
  }
  
  getAccountInfo() {
    return `Account: ${this.#maskedAccountNumber}, Balance: $${this.#balance}`;
  }
}

const account = new BankAccount('123456789', 1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// console.log(account.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class

8. 实例属性定义

传统方式
class Counter {
  constructor() {
    this.count = 0;
  }
  
  increment() {
    this.count++;
  }
}
现代方式(ES2022)
class Counter {
  count = 0; // 实例属性
  
  increment() {
    this.count++;
  }
  
  // 箭头函数方法(自动绑定 this)
  handleClick = () => {
    this.increment();
  }
}

9. Mixins 模式

// Mixin 工厂函数
const Flyable = (superclass) => class extends superclass {
  fly() {
    console.log('Flying...');
  }
};

const Swimmable = (superclass) => class extends superclass {
  swim() {
    console.log('Swimming...');
  }
};

// 使用 Mixins
class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Duck extends Swimmable(Flyable(Animal)) {
  constructor(name) {
    super(name);
  }
  
  quack() {
    console.log('Quack!');
  }
}

const duck = new Duck('Donald');
duck.fly();   // "Flying..."
duck.swim();  // "Swimming..."
duck.quack(); // "Quack!"

10. Class 表达式

// 匿名 Class 表达式
const MyClass = class {
  constructor(name) {
    this.name = name;
  }
};

// 命名 Class 表达式
const MyClass = class NamedClass {
  constructor(name) {
    this.name = name;
  }
  
  getName() {
    return this.name;
  }
};

// 立即执行 Class
const instance = new class {
  constructor() {
    this.name = 'Anonymous';
  }
  
  greet() {
    return `Hello, ${this.name}`;
  }
}();

console.log(instance.greet()); // "Hello, Anonymous"

11. Class 的 this 绑定

class Button {
  constructor(element) {
    this.element = element;
    this.clickCount = 0;
    
    // 箭头函数自动绑定 this
    this.element.addEventListener('click', this.handleClick);
  }
  
  // 箭头函数方法
  handleClick = () => {
    this.clickCount++;
    console.log(`Clicked ${this.clickCount} times`);
  }
  
  // 普通方法需要手动绑定
  handleHover() {
    console.log('Hovered');
  }
}

// 使用
const button = new Button(document.getElementById('myButton'));

12. Class 与原型链

class Parent {
  parentMethod() {
    return 'parent';
  }
}

class Child extends Parent {
  childMethod() {
    return 'child';
  }
}

const child = new Child();

// 原型链关系
console.log(child.__proto__ === Child.prototype);           // true
console.log(Child.prototype.__proto__ === Parent.prototype); // true
console.log(Parent.prototype.__proto__ === Object.prototype); // true

// 方法查找
console.log(child.childMethod());  // "child"
console.log(child.parentMethod()); // "parent"

13. 实际应用示例

// 完整的用户管理系统示例
class UserManager {
  // 私有属性
  #users = new Map();
  #nextId = 1;
  
  // 静态属性
  static DEFAULT_ROLE = 'user';
  
  // 实例属性
  name = 'UserManager';
  
  addUser(userData) {
    const id = this.#nextId++;
    const user = {
      id,
      ...userData,
      role: userData.role || UserManager.DEFAULT_ROLE,
      createdAt: new Date()
    };
    
    this.#users.set(id, user);
    return user;
  }
  
  getUser(id) {
    return this.#users.get(id);
  }
  
  removeUser(id) {
    return this.#users.delete(id);
  }
  
  getAllUsers() {
    return Array.from(this.#users.values());
  }
  
  // 静态方法
  static validateEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
  
  // Getter
  get userCount() {
    return this.#users.size;
  }
}

// 使用示例
const userManager = new UserManager();
const user = userManager.addUser({
  name: 'Alice',
  email: 'alice@example.com'
});

console.log(userManager.userCount); // 1
console.log(UserManager.validateEmail('test@example.com')); // true

当然可以!ES6 的 class 虽然让 JavaScript 的面向对象编程更清晰、更接近传统语言,但它本质上仍然是基于原型(prototype)的语法糖。如果不了解其底层机制和注意事项,很容易写出“看似正确但实际有问题”的代码。


使用注意事项

1. class 不是真正的“类” —— 它是语法糖

ES6 的 class 只是构造函数的语法糖,底层依然是原型链。

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log(`Hello, ${this.name}`);
  }
}

// 等价于:
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function () {
  console.log(`Hello, ${this.name}`);
};

注意class 内部方法不会被枚举(enumerable: false),这是与手动添加方法的区别。


2. 必须使用 new 调用,否则报错
class Person {
  constructor(name) {
    this.name = name;
  }
}

Person('Alice'); // TypeError: Class constructor Person cannot be invoked without 'new'

必须:new Person('Alice')


3. 不存在变量提升(No Hoisting)
const p = new Person('Alice'); // ReferenceError

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

class 声明不会提升,必须先声明再使用(类似 let/const)。


4. 类内部默认严格模式
class Person {
  constructor(name) {
    // 自动启用严格模式
    name = 'Alice'; // 正常
    undeclared = 'test'; // ReferenceError,严格模式下不能隐式创建全局变量
  }
}

好事,避免意外错误。


5. this 指向问题 —— 方法解构后丢失上下文
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log(`Hello, ${this.name}`);
  }
}

const p = new Person('Alice');
p.sayHello(); // ✅ Hello, Alice

const { sayHello } = p;
sayHello(); // Hello, undefined(this 指向丢失)
解决方案:
方案 1:箭头函数绑定(在 constructor 中)**
class Person {
  constructor(name) {
    this.name = name;
    this.sayHello = () => {
      console.log(`Hello, ${this.name}`);
    };
  }
}
方案 2:手动绑定
const boundHello = p.sayHello.bind(p);
boundHello(); 
方案 3:使用 bind 在解构时
const { sayHello } = p;
sayHello.call(p); 

6. 私有字段必须显式声明
class Person {
  constructor(name) {
    this.#name = name; // SyntaxError: Private field '#name' must be declared in an enclosing class
  }
}

必须先声明:

class Person {
  #name;
  constructor(name) {
    this.#name = name; 
  }
}

7. 静态方法不能访问实例属性/方法
class Person {
  constructor(name) {
    this.name = name;
  }
  static greet() {
    console.log(`Hello, ${this.name}`); // this 指向类,不是实例
  }
}

静态方法只能访问静态属性或传入的参数。


8. 继承时必须调用 super()
class Student extends Person {
  constructor(name, grade) {
    // 必须先调用 super()
    this.grade = grade; // ReferenceError
    super(name);
  }
}

正确:

class Student extends Person {
  constructor(name, grade) {
    super(name); // 必须先调用
    this.grade = grade;
  }
}

9. super 的使用限制
  • super 必须在 constructor 中调用 super() 才能访问 this
  • super.method() 可以调用父类方法
  • super 不能单独使用(如 console.log(super)

10. class 不能枚举,但方法可以被继承
for (let key in Person) {
  console.log(key); //  不会输出任何东西(静态方法除外)
}

实例方法在原型链上,可被继承。


11. getter/setter 是属性,不是方法
obj.prop(); // 错误,不能加 ()
obj.prop = value; // 正确

12. 不要在 getter 中有副作用
get name() {
  this.#visitCount++; //  不该在 getter 中修改状态
  return this.#name;
}

getter 应该是纯读取操作。


最佳实践总结
场景 建议
定义类 使用 class + constructor
私有数据 使用 #privateField
封装访问 get/set + 私有字段
方法绑定 constructor 中用箭头函数或 bind
继承 extends + super()
静态成员 static,只访问静态数据
调用方式 必须 new
命名 避免与 getter/setter 同名
调试 注意 this 指向和 super 调用

一句话总结
ES6 class 让代码更清晰,但本质仍是原型继承。使用时要注意:

  • 必须 new
  • this 可能丢失
  • 私有字段需声明
  • 继承要调 super
  • getter/setter 是属性

总结

ES6 Class 的主要优势:

  1. 语法更清晰:更接近传统面向对象语言
  2. 更好的封装:支持私有属性和方法
  3. 继承更简单:使用 extendssuper 关键字
  4. 静态成员:支持静态方法和属性
  5. Getter/Setter:提供属性访问控制
  6. 更好的工具支持:IDE 和调试工具支持更好

更多推荐