你以为你写了 class,实际上是 prototype 在工作。
一、一段看起来正常的 class 代码
下面是一个简单的类,用来统计按钮点击次数:
class Counter {
count = 0;
inc() {
this.count++;
console.log('当前 count:', this.count);
}
}
const counter = new Counter();
const fn = counter.inc.bind(counter); // 关键:绑定 this
fn(); // ✅ 输出:当前 count: 1
看起来没有问题,但如果你写成:
const fn = counter.inc;
fn(); // ❌ TypeError: Cannot read properties of undefined
就会达到一个初学者常见的坑:this 丢失问题。
二、问题本质:class 里的方法本质是 prototype 上的
class Counter {
count = 0;
inc() {
this.count++;
}
}
但实际上,类里的 inc() 方法,等同于写在原型上:
Counter.prototype.inc
所以当你写成:
const fn = counter.inc;
fn();
你操作的是 Counter.prototype.inc(),它被调用时的 this,并不是 counter。
三、ES2020 的 class:私有字段和私有方法的加入
ES2020 引入了 # 前缀的私有字段和方法,它们不会存在于 prototype 上,而是存储在每个实例的私有槽中:
class Counter {
#count = 0; // 私有字段
#log() { // 私有方法
console.log('当前 count:', this.#count);
}
inc() {
this.#count++;
this.#log();
}
}
这些私有成员:
- ❌ 无法从外部访问
- ❌ 不存在于 prototype 上
- ✅ 更安全,封装性更强
但注意:类中的普通方法(如 inc() )仍然在 prototype 上,只有 #私有 的才在实例内部。
所以你可以理解为:
class是对原型机制的封装升级,#私有是对封装性的增强补丁。
四、热门问题:class 里的 arrow function 有用吗?
有人会说:不如写成箭头函数,这样 this 就绑定了:
class Counter {
count = 0;
inc = () => {
this.count++;
}
}
确实可行,但他是属于对象本身,而非 prototype,每创建一个对象,都会重复一份 inc 方法。
| 写法 | 内存开销 | 是否在 prototype 上 | this 安全 |
|---|---|---|---|
| 普通方法 | ✅ 小 | ✅ 是 | ❌ 可能丢失 |
| 箭头函数属性 | ❌ 大 | ❌ 否 | ✅ 永远安全 |
五、实际应用中,你应该怎么写
想规范写法:绑定 this
class Counter {
count = 0;
constructor() {
this.inc = this.inc.bind(this);
}
inc() {
this.count++;
}
}
想便捷写法:直接箭头函数
class Counter {
count = 0;
inc = () => {
this.count++;
}
}
不想把方法抽离 context:直接调用
counter.inc();
六、class 真的是坑吗?
class 本质是 function + prototype 的糖装。
它不是坑,当你明白了它的原理时,很好用:
- 用来类型分层
- 用来构造构件库 API
- 用来作为 DSL 配置的配置器
但当你不明白 prototype 时,用 class 就可能成为坑:
- this 丢失
- super 指向混乱
- 继承时 prototype chain 未明确
🧠 什么是“非类化语言”?
不是说这个语言没有类(如 JS 后来也支持 class),而是指:
这个语言的底层并不依赖 class 来实现封装、继承、组合与复用。
JavaScript 就是典型代表 —— 它底层基于原型链(prototype chain)和函数闭包,不是 class。
七、总结:看懂了这个 class Counter,你就明白了 JS 的编程本质
JS 不高级吗?是因为“自由”让人误判了。JavaScript 不是“不高级”,而是它: 允许你写得很烂,也允许你写得极致优雅。
- 你可以用 class 模拟面向对象
- 你也可以用闭包模拟封装
- 你还可以用高阶函数、柯里化、组合函数写函数式代码
这是一种 “可进可退、无框架依赖的原生表达力”。不是写 class 就是面向对象; 也不是用 prototype 就是老时代。
真正强大的前端工程师,是知道:
在哪些场景下,该用 class;在哪些场景下,应该先用组合和函数