ES6 Class

126 阅读2分钟

一、Class 基本语法

1. 类声明与类表达式

ES6 引入了更接近传统语言的类声明方式,但本质上仍然是基于原型的语法糖。

// 类声明
class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayHello() {
    return `Hello, ${this.name}`;
  }
}

// 类表达式
const Animal = class {
  constructor(type) {
    this.type = type;
  }
};

2. 构造方法

constructor 方法是类的默认方法,通过 new 命令生成对象实例时自动调用。

class Point {
  constructor(x, y) {
    this.x = x;  // 实例属性
    this.y = y;
  }
  
  toString() {
    return `(${this.x}, ${this.y})`;
  }
}

const p = new Point(1, 2);
console.log(p.toString()); // (1, 2)

二、Class 的核心特性

1. 方法定义

类的方法都定义在类的 prototype 属性上,实例调用方法实际上就是调用原型上的方法。

class Person {
  constructor() {}
  speak() {}
  eat() {}
}

// 等同于
Person.prototype = {
  constructor() {},
  speak() {},
  eat() {}
};

2. 静态方法与属性

静态方法/属性使用 static 关键字修饰,不会被实例继承,而是直接通过类调用。

class MyClass {
  static staticMethod() {
    return 'static method';
  }
  
  static staticProperty = 'static property';
}

console.log(MyClass.staticMethod()); // "static method"
console.log(MyClass.staticProperty); // "static property"

3. 私有字段与方法

ES2022 正式为 class 添加了私有属性和方法,在属性名之前使用 # 表示。

class Counter {
  #count = 0;  // 私有字段
  
  #increment() {  // 私有方法
    this.#count++;
  }
  
  tick() {
    this.#increment();
    console.log(this.#count);
  }
}

const c = new Counter();
c.tick(); // 1
// c.#count;  // SyntaxError

三、Class 的继承机制

1. extends 继承

使用 extends 关键字实现继承,比 ES5 的原型链继承更清晰。

class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

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

const d = new Dog('Rex', 'Labrador');
d.speak();
// Rex makes a noise.
// Rex barks.

2. super 关键字

super 有两种使用方式:

  • 作为函数调用(super()),代表父类构造函数
  • 作为对象调用(super.method()),指向父类原型对象
class Parent {
  constructor() {
    this.name = 'Parent';
  }
  
  say() {
    return this.name;
  }
}

class Child extends Parent {
  constructor() {
    super();  // 必须在使用this前调用
    this.childName = 'Child';
  }
  
  say() {
    return super.say() + ' & ' + this.childName;
  }
}

四、Class 的底层实现

1. 与 ES5 原型继承的对应关系

// ES6
class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayName() {
    console.log(this.name);
  }
}

// 等同于 ES5
function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  console.log(this.name);
};

2. 继承的底层实现

// ES6
class Animal {}
class Dog extends Animal {}

// 等同于 ES5
function Animal() {}
function Dog() {}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Object.setPrototypeOf(Dog, Animal);

五、Class 的注意事项

1. 不存在变量提升

new Foo(); // ReferenceError
class Foo {}

2. this 指向问题

类方法内部的 this 默认指向实例,但单独提取方法可能改变 this 指向。

class Logger {
  printName(name = 'default') {
    this.print(`Hello ${name}`);
  }
  
  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined

解决方案:

// 1. 在构造函数中绑定this
class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }
}

// 2. 使用箭头函数
class Logger {
  printName = (name = 'default') => {
    this.print(`Hello ${name}`);
  }
}

// 3. 使用Proxy自动绑定

六、Class 的高级特性

1. 存取器(getter/setter)

class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  
  set fullName(value) {
    [this.firstName, this.lastName] = value.split(' ');
  }
}

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

2. 类字段声明语法

ES2022 新增的类字段提案:

class Counter {
  count = 0;  // 实例字段
  
  increment = () => {  // 箭头函数方法
    this.count++;
  };
  
  static version = '1.0';  // 静态字段
}

3. 静态初始化块

ES2022 引入静态初始化块,用于类静态端的复杂初始化。

class Translator {
  static translations = {
    yes: 'ja',
    no: 'nein',
    maybe: 'vielleicht',
  };
  
  static englishWords = [];
  static germanWords = [];
  
  static {  // 静态初始化块
    for (const [english, german] of Object.entries(this.translations)) {
      this.englishWords.push(english);
      this.germanWords.push(german);
    }
  }
}

七、Class 的应用场景

1. 框架中的组件设计

React 类组件:

class MyComponent extends React.Component {
  state = { count: 0 };
  
  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };
  
  render() {
    return (
      <button onClick={this.handleClick}>
        Clicked {this.state.count} times
      </button>
    );
  }
}

2. 复杂业务模型

class ShoppingCart {
  #items = [];
  
  addItem(item) {
    this.#items.push(item);
  }
  
  removeItem(id) {
    this.#items = this.#items.filter(item => item.id !== id);
  }
  
  get total() {
    return this.#items.reduce((sum, item) => sum + item.price, 0);
  }
  
  static createFromJSON(json) {
    const cart = new ShoppingCart();
    JSON.parse(json).forEach(item => cart.addItem(item));
    return cart;
  }
}

八、Class 与原型的关系总结

  1. 语法糖本质:class 本质上是构造函数的语法糖
  2. 继承实现:extends 实现了基于原型的继承
  3. 方法存储:类方法存储在原型对象上
  4. 静态成员:静态方法/属性存储在构造函数上
  5. 实例属性:constructor 内定义的属性是实例自有属性
class Example {
  instanceProperty = 'instance';
  static staticProperty = 'static';
  
  instanceMethod() {}
  static staticMethod() {}
}

// 等价关系
console.log(typeof Example); // "function" (构造函数)
console.log(Example.prototype.instanceMethod); // function
console.log(Example.staticMethod); // function
console.log(Example.prototype.constructor === Example); // true