前端面试题之Class详解
一文详解 ES6 中的 Class:语法糖与面向对象新范式
ES6 引入的 class
关键字是 JavaScript 面向对象编程的重大革新。它并非全新的继承模型,而是基于原型链的语法糖,让开发者以更清晰、更接近传统语言(如 Java/C++)的方式定义类和实现继承。本文将深入剖析其核心特性和使用场景。
一、Class 基础语法与本质
1. 类定义方式
-
类声明:
class Point {constructor(x, y) {this.x = x; // 实例属性this.y = y;}toString() { // 原型方法return `(${this.x}, ${this.y})`;} }
-
类表达式(匿名或命名):
const Point = class { /* ... */ }; // 匿名 const Point = class NamedPoint { /* ... */ }; // 命名
注意:
-
类不可重复声明,且不存在变量提升(必须先定义后使用)。
-
方法间无需逗号分隔,且不可枚举(与 ES5 构造函数的方法不同)。
2. 核心组成剖析
-
constructor
方法:
类的默认构造方法,new
实例化时自动调用。若未显式定义,引擎会添加空constructor
。其返回值默认为实例对象 (this
),也可手动返回其他对象(但会破坏实例关系)。class Foo {constructor() {return { custom: "object" }; // 返回非实例对象} } new Foo() instanceof Foo; // false
-
实例属性与方法:
-
显式定义在
this
上的属性属于实例(如this.x
)。 -
类内直接定义的方法(如
toString()
)实际绑定到prototype
上,由所有实例共享。
-
-
静态方法与属性:
通过static
关键字定义,属于类本身而非实例:class MathUtils {static add(a, b) { return a + b; } // 静态方法static PI = 3.14; // 静态属性(ES2022+) } MathUtils.add(1, 2); // 3
⚠️ 注意:ES6 规范仅支持静态方法,静态属性需通过
ClassName.prop = value
或 ES2022+ 新语法实现
二、继承机制:extends 与 super
1. 实现继承
使用 extends
建立子类与父类的继承关系,子类必须在 constructor
中调用 super()
:
class ColorPoint extends Point {constructor(x, y, color) {super(x, y); // 调用父类构造函数,初始化父类属性this.color = color; // 子类新增属性}toString() {return `${this.color} ${super.toString()}`; // super 调用父类方法}
}
关键规则:
-
super()
必须在this
前调用:确保子类实例基于父类实例构建。 -
ES6 继承本质:先创建父类实例(通过
super
),再修饰为子类实例(“继承在前,实例在后”),与 ES5 的原型链继承(“实例在前,继承在后”)机制不同。
2. super 的双重角色
-
作为函数:
super()
调用父类构造函数。 -
作为对象:
super.method()
调用父类原型方法(如super.toString()
)。
三、高级特性与注意事项
1. 原型链与 prototype
-
类的所有方法仍定义在
prototype
上:class Point { /* ... */ } typeof Point; // "function"(本质是构造函数) Point === Point.prototype.constructor; // true
-
可通过
Object.assign()
动态添加方法:Object.assign(Point.prototype, {log() { console.log(this.x, this.y) } });
2. 访问器属性(Getter/Setter)
拦截属性的读写操作:
class Person {constructor(name) {this._name = name;}get name() { return this._name.toUpperCase(); }set name(value) { this._name = value; }
}
3. 其他特性
-
name
属性:返回类名(命名类表达式以表达式的名为准)。 -
this
指向问题:类方法中的this
默认指向实例。若单独提取方法(如const { toString } = point
),可能因丢失上下文而报错。解决方案:-
构造器中绑定
this
:this.method = this.method.bind(this)
-
使用箭头函数:
method = () => { /* ... */ }
(实例方法)。
-
四、对比 ES5 与最佳实践
1. 与 ES5 构造函数的区别
特性 | ES6 Class | ES5 构造函数 |
---|---|---|
方法可枚举性 | 不可枚举 | 可枚举 |
调用方式 | 必须 new ,否则报错 | 可不加 new (当普通函数) |
继承语法 | extends /super 清晰 | 手动操作原型链(复杂易错) |
2. 实践建议
- 优先使用 Class 语法:提升代码可读性和维护性。
- 避免滥用继承:组合优于继承,减少层级过深。
- 静态属性兼容性:旧环境可用
ClassName.prop = value
替代 ES2022+ 静态属性语法。
五、底层原理:原型链视角
Class 的本质仍是基于原型的继承:
class A {}
class B extends A {}// 构造继承
B.__proto__ === A; // true// 方法继承
B.prototype.__proto__ === A.prototype; // true
这种双原型链结构保证了:
- 子类能继承父类的静态方法(通过
B.__proto__ = A
) - 实例能访问父类原型方法(通过
B.prototype.__proto__ = A.prototype
)38
六、重要限制与注意事项
-
不存在变量提升
new MyClass(); // ReferenceError class MyClass {}
-
不可重复定义同名类
class Foo {} class Foo {} // SyntaxError
-
方法不可枚举
class Point {toString() {} } Object.keys(Point.prototype); // []
-
必须使用 new 调用
Point(); // TypeError
-
静态属性和方法不会被实例继承
class Util {static version = '1.0'; } new Util().version; // undefined
七、最佳实践总结
- 优先使用 class 语法:比原型链写法更清晰易读
- 始终在子类构造函数中先调用 super() :避免 this 引用错误
- 合理使用静态成员:存放与类相关但独立于实例的工具方法
- 私有字段以 # 开头:遵循 ES2022 标准实现封装
- 避免过度使用继承:组合优于继承,考虑使用 Mixin
- 注意 this 的指向问题:类方法中的 this 默认指向实例
ES6 的 class
通过语法糖形式统一了 JavaScript 的面向对象写法,其核心仍是基于原型的继承机制。掌握 constructor
、extends
、super
、static
等关键字,理解实例/原型/静态成员的区别,能显著提升大型应用的代码组织能力。随着装饰器(Decorator)等提案的推进,类的功能仍在不断演进。