ES6 Class特性解析

74 阅读3分钟

一、Class 的本质

ES6 的 class 本质上是构造函数的语法糖,其功能大部分可通过 ES5 实现。新语法旨在使对象原型的写法更清晰,更符合面向对象编程习惯。


二、核心特性详解

1. 原型方法与属性定义

  • 类中所有方法(除非显式定义在 this 上)均定义在类的 prototype 对象上。
  • 类的 prototype.constructor 属性指向类本身(与构造函数一致)。
  • 重要区别:类中定义的方法不可枚举(ES5 构造函数的方法可枚举)。

2. 构造函数

  • constructor() 是类的默认方法,new 实例化时自动调用。
  • 若未显式定义,JS 引擎会自动添加一个空的 constructor()

3. 实例化要求

  • 必须使用 new 调用,直接调用类(如 MyClass())会抛出错误。

4. 实例属性定义方式

  • 传统方式:在 constructor() 内通过 this 定义。

    class Person {
      constructor() {
        this.name = 'John'; // 实例属性
      }
    }
    
  • ES2022 新方式:直接在类顶层声明(推荐)。

    class Person {
      name = 'John'; // 顶层实例属性
    }
    

5. 访问器属性

  • 使用 get/set 拦截属性存取:
    class Square {
      get html() {
        return this.element.innerHTML;
      }
      set html(value) {
        this.element.innerHTML = value;
      }
    }
    

6. 动态属性名

  • 属性名可使用表达式:
    let methodName = 'getArea';
    class Square {
      [methodName]() { /* ... */ } // 动态方法名
    }
    

7. 类表达式

  • 类可通过表达式定义,类名(内部名)仅在内部可见:

    const MyClass = class Me {
      getClassName() {
        return Me.name; // 'Me' 仅在类内部可用
      }
    };
    let inst = new MyClass();
    inst.getClassName(); // "Me"
    console.log(Me.name); // 错误:Me 未定义
    

8. 静态成员

  • 用 static 关键字定义:

    • 静态方法/属性属于类本身,实例不可继承。
    • 父类静态方法可被子类继承。
    class MyClass {
      static staticMethod() { return 'Hello'; }
      static staticProp = 42;
    }
    MyClass.staticMethod(); // "Hello"
    

9. 私有属性(ES2022)

  • 属性名前加 # 表示私有:

    • 仅在类内部可访问。
    • 访问不存在的私有属性会报错。
    • 允许在实例方法中通过 this.#prop 访问。
    class Counter {
      #count = 0; // 私有属性
      increment() {
        this.#count++;
      }
    }
    

10. 类继承 (extends 与 super)

  • 通过 extends 实现继承。

  • 子类必须在 constructor 中调用 super() 才能使用 this

  • 与 ES5 继承的关键区别

    • ES5 继承:先创建子类实例 (this),再通过 Parent.call(this) 继承父类属性。
    • ES6 继承:先调用 super() 创建父类实例(建立 this),再初始化子类。
    class Parent {}
    class Child extends Parent {
      constructor() {
        super(); // 必须先调用 super()
        this.childProp = 1;
      }
    }
    

    需要注意的是ES6 的 class 继承在语法上表现为先继承(通过 super())再初始化实例,但是在底层实现上还是先创建实例再设置原型链继承。

    class A {
      constructor() {
        console.log('A constructor start - this:', this);
        this.a = 'a';
        console.log('A constructor end');
      }
    }
    
    class B extends A {
      constructor() {
        console.log('B constructor start - this:', this); // ❌ 这里不能访问 this
    
        // 1. 调用 super() 相当于:
        //    - 创建实例对象
        //    - 设置 B.prototype -> A.prototype 的原型链
        //    - 执行 A 构造函数
        super(); 
    
        console.log('B constructor after super - this:', this); // ✅ 可以访问 this
    
        // 2. 初始化子类属性
        this.b = 'b';
        console.log('B constructor end');
      }
    }
    
    // 验证原型链
    console.log('B.prototype.__proto__ === A.prototype:', 
                B.prototype.__proto__ === A.prototype); // true
    
    // 创建实例
    const obj = new B();
    
    /* 输出顺序:
    B constructor start - this: undefined
    A constructor start - this: B {}
    A constructor end
    B constructor after super - this: B { a: 'a' }
    B constructor end
    */
    


### 三、总结

| 特性        | 关键点                                             |
| --------- | ----------------------------------------------- |
| **原型方法**  | 定义于 `prototype`,方法不可枚举                          |
| **构造函数**  | 必需,未定义时自动添加空函数                                  |
| **实例化**   | 必须使用 `new`                                      |
| **实例属性**  | 支持传统 (`constructor` + `this`) 和 ES2022 顶层定义方式   |
| **访问器**   | `get`/`set` 实现属性存取拦截                            |
| **动态属性名** | 使用 `[expression]` 语法                            |
| **类表达式**  | 类名仅在内部可见                                        |
| **静态成员**  | `static` 定义,类直接访问,可继承                           |
| **私有属性**  | `#` 前缀,仅类内部访问                                   |
| **继承机制**  | `extends` + `super()`,先创建父类实例再初始化子类(与 ES5 时序相反) |

> **核心价值**:`class` 语法提供了更清晰、更安全的面向对象编程模式,通过规范化语法减少原型链操作错误,并引入私有属性等现代化特性,显著提升代码可维护性。