ES6 class 的本质是什么?和原型有什么关系?
简化版
ES6 class 本质上仍然基于原型,是构造函数和原型继承的语法糖。类方法定义在 prototype 上,extends 建立子类和父类之间的原型关系,super 用来调用父类构造函数或方法。
详细版
示例:
class Person {
constructor(name) {
this.name = name;
}
say() {
return this.name;
}
}
say 实际在 Person.prototype 上,而不是每个实例一份。
继承:
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
}
子类构造函数里必须先调用 super() 才能使用 this。class 不会变量提升,类体默认严格模式。
完整版教学
一、class 改善的是写法
ES5 写构造函数和原型方法比较分散:
function Person(name) {
this.name = name;
}
Person.prototype.say = function () {};
class 把构造函数、实例方法、静态方法集中起来,可读性更好,但底层对象模型仍是原型。
二、实例方法和静态方法
类体中普通方法放在原型上,实例共享:
class A {
foo() {}
}
静态方法属于类本身:
class A {
static create() {}
}
实例不能直接访问静态方法。
三、extends 做了什么
extends 建立两条关系:子类实例能访问父类原型方法;子类构造函数也能继承父类静态方法。
super() 在构造函数里调用父类构造器;super.method() 在方法里调用父类原型方法。派生构造器的 this 在 super() 完成前尚未初始化,因此提前读取或返回默认的 this 都会失败。
四、class 字段和箭头方法
现代 JS 支持类字段:
class Counter {
count = 0;
inc = () => {
this.count++;
};
}
箭头方法能绑定实例 this,但每个实例都会创建一份函数,不像原型方法共享。要按场景选择。
五、面试追问与工程落地
class 常见追问是“类声明有没有提升”。类声明存在类似提升的绑定,但在初始化前处于暂时性死区,不能像函数声明一样在声明前安全调用。这一点和 let/const 的行为更接近。
还会追问私有字段。现代 JS 支持 #field 私有字段,它是语言层面的私有,不是 TypeScript 的 private 那种编译期限制。#count 只能在类内部访问,外部无法通过普通属性名读取。
工程里要注意 class 字段箭头函数和原型方法的取舍。箭头字段自动绑定 this,但每个实例一份;原型方法共享,但传给回调时可能丢 this。写组件、SDK、工具类时要按实例数量和回调场景选择。
六、从原型链还原 class 的真实语义
class 仍使用原型链组织继承:实例方法位于 Ctor.prototype,静态方法位于构造器对象,实例通过 [[Prototype]] 找到共享方法。但它不是普通构造函数的逐字符替换:类体默认严格模式,类声明存在暂时性死区,类方法默认不可枚举,类构造器也不能脱离 new 直接调用。
class User {
login() {}
static create() { return new User() }
}
const user = new User()
console.log(Object.getPrototypeOf(user) === User.prototype) // true
console.log(Object.hasOwn(user, 'login')) // false
console.log(Object.hasOwn(User, 'create')) // true
| 成员写法 | 存放位置 | 1000 个实例通常有几份函数 |
|---|---|---|
login() {} | User.prototype | 1 |
static create() {} | User | 1 |
login = () => {} | 每个实例 | 1000 |
派生类构造器在使用 this 前必须调用 super(),因为基类构造过程负责创建并初始化本次实例。私有字段 #name 由语言在运行时做品牌检查,不等同于 _name 命名约定,也不能通过普通字符串属性访问。
面试回答“class 是语法糖”后必须补充严格模式、不可直接调用、方法不可枚举和私有字段等额外语义,否则容易被追问击穿。
七、常见误区与追问
- 误区:class 创建了不同于原型链的新继承模型。 它提供类式语法,底层实例查找仍依靠原型链。
- 误区:类里的所有方法都在实例上。 普通实例方法共享在原型上,只有实例字段和字段箭头函数等会落到实例自身。
- 误区:static 方法能直接访问实例字段。 静态方法的接收者是类构造器,没有某个实例的状态,除非显式接收实例。
- 追问:为什么派生类必须先调用 super? 在派生构造器中,
this要等基类构造完成后才被初始化,提前访问会抛出ReferenceError。 - 追问:类方法被解构后为什么可能丢失 this? 调用位置不再提供实例接收者;可在调用处绑定,或有意识地使用实例箭头字段。
- 追问:私有字段和下划线字段有什么区别?
#field有语言级封装和品牌检查,_field只是团队约定,外部仍可读写。 - 追问:继承静态成员和继承实例成员是同一条链吗? 不是;构造器之间和各自的
prototype对象之间形成两条相关的原型链。
八、加强记忆
class 是“原型继承穿西装”。语法像传统面向对象,底层仍靠原型链;普通方法在 prototype,静态方法在类本身,子类用 super 连接父类逻辑。
最终要能从实例成员、原型成员和静态成员三处定位属性,才能真正解释方法共享、继承与 this。