JavaScript 原型不是 class
对照经典类继承与原型委托,说明 class 语法糖的边界,避免把 Java/C# 心智硬套到 JS。
#synthesis
#growing
#dev
#javascript
#ecmascript
[!info] related notes
- 所属:类型与对象模型 MOC
- 前置:原型链 · class
- 相关:ES6/ES5 继承对照
JavaScript 原型不是 class
这个概念为什么出现
从 Java/C# 转来的同学看到 class Sku {} 就以为:
- 实例按类蓝图复制成员
- 私有/类型系统与运行时一一对应
- 继承是类型层级的唯一故事
然后在 this、装饰数据、运行时改原型时全面撞墙。
[!abstract] 一句话理解 JS 的属性继承建立在原型委托上;class 使用同一对象模型,同时提供严格模式、构造限制和私有元素等语义。
最小对照示例
场景:价格格式化既要共享方法,又要实例自有币种
// 原型委托:查找时顺着链接走
const pricing = {
currency: 'CNY',
format(amount) {
return amount + ' ' + this.currency
},
}
const skuPricing = Object.create(pricing)
skuPricing.currency = 'USD' // 写在自身,不影响原型默认
console.log(skuPricing.format(12)) // 12 USD
console.log(pricing.format(12)) // 12 CNY
// class 语法仍然生成原型关系
class Pricing {
constructor(currency) {
this.currency = currency
}
format(amount) {
return amount + ' ' + this.currency
}
}
class SkuPricing extends Pricing {}
const p = new SkuPricing('EUR')
console.log(Object.getPrototypeOf(p) === SkuPricing.prototype)
console.log(Object.getPrototypeOf(SkuPricing.prototype) === Pricing.prototype)
核心对照
| 维度 | 经典类语言直觉 | JavaScript |
|---|---|---|
| 方法存放 | 类上的成员定义 | 通常在原型上共享 |
| 继承 | 类型系统 + 布局 | 对象链接委托 |
| 扩展 | 编译期类型 | 运行时也可改对象形状 |
| class 关键字 | 类型定义语法(具体语言规则不同) | 建立原型关系,并施加构造与方法语义约束 |
class 不能简单替换为“给 prototype 赋几个函数”:
- 类体总在严格模式下执行,类构造器必须通过 new 等构造调用进入。
- 实例方法通常在原型上且不可枚举;公有实例字段是实例自身属性,静态成员在构造函数上。
#private元素有运行时身份检查,不能通过普通属性名访问。- Java/C# 通常也共享方法代码;“类语言为每个实例复制方法”本身就是错误前提。
常见误区
[!warning] 常见误区:先背 extends 再理解对象 应先会画
[[Prototype]]链,再学 class 语法。
工程实践
- 代码风格可用 class,调试用原型。
- 需要真正封装时考虑闭包模块、
#private字段,而不是假装 Java。 - TypeScript 类型层级 ≠ 运行时原型层级的完整替代。
本节总结
用原型链解释属性委托,用 class 规则解释构造、字段与封装;两层都需要理解。
自测题
extends在运行时主要建立了什么?
参考答案
对示例 class B extends A:B 的原型是 A,B.prototype 的原型是 A.prototype,实例的原型是 B.prototype;分别支持静态成员与实例方法的继承。派生构造器还需遵守 super 初始化规则。