JavaScript 原型链
解释对象属性查找如何沿原型链上升、与 constructor/prototype 的关系,以及 class 语法糖如何落到同一模型。
#type / concept
#status / growing
#tech / dev
#resource / javascript
#resource / ecmascript
[!info] 关联笔记
- 所属:类型与对象模型 MOC · ECMAScript MOC
- 路线:JavaScript 学习路线
- 前置:对象 · 原型
- 后续:原型不是 class · new · class · this
JavaScript 原型链
这个概念为什么出现
你在对象上读 sku.toString,自己明明没写这个方法,却能调用。团队若把这理解成“类继承复制成员”,后面在改 prototype、谈性能、写 polyfill 时会全面错位。
原型链回答:属性从哪里来,找不到时下一步去哪找。
[!abstract] 一句话理解 每个对象有一个内部原型链接;读属性时沿这条链向上查找,直到找到或到达
null。
最小可运行示例
场景:库存领域对象共享格式化方法
// 业务意图:所有 SKU 视图模型共享 formatStock,不在每个实例上复制函数。
// 教学点:实例自身没有的方法,会走到构造函数 prototype。
function SkuView(code, qty) {
this.code = code
this.qty = qty
}
SkuView.prototype.formatStock = function formatStock() {
// 教学点:这里的 this 由调用方式决定,一般是实例
return this.qty < 5
? this.code + ' 低库存(' + this.qty + ')'
: this.code + ' 正常(' + this.qty + ')'
}
const a = new SkuView('A-01', 3)
const b = new SkuView('B-02', 20)
console.log(a.formatStock())
console.log(Object.hasOwn(a, 'formatStock')) // false:方法不在实例自身
console.log(Object.getPrototypeOf(a) === SkuView.prototype) // true
console.log(a.toString === Object.prototype.toString) // 继续沿链找到 Object.prototype
建议运行:
node sku-proto.js
期望输出:
A-01 低库存(3)
false
true
true
结合场景再看三个关注点
- 共享方法放在
Fn.prototype,实例变轻。 - 查找失败不是报错在第一层,而是继续
[[Prototype]]。 hasOwn/in不同:in会看链,hasOwn只看自身。
核心概念与准确模型
两条相关但不同的线
| 概念 | 是什么 |
|---|---|
实例的 [[Prototype]] | 对象的原型链接(Object.getPrototypeOf) |
函数的 .prototype | 作为构造函数时,new 出来的实例原型指向它 |
obj.__proto__ | 历史访问器,教学可用,生产更推荐标准 API |
a ---> SkuView.prototype ---> Object.prototype ---> null
读属性算法(压缩)
- 看对象自身
- 看
[[Prototype]] - 重复直到
null - 若是方法调用,
this仍由调用方式决定,不由方法所在原型决定
写属性
默认赋值写在自身(除非 setter 在链上)。这就是“改实例不会改共享原型方法”的基础。
与 class 的关系
class SkuView {
constructor(code, qty) {
this.code = code
this.qty = qty
}
formatStock() {
/* 方法在原型上 */
}
}
class 是语法层;运行时仍是原型模型。见 原型不是 class。
设计动机
原型共享让方法与默认值可复用,又保持实例数据独立。这是 JS 对象系统的历史与性能折中,而不是 Java 的拷贝式继承。
边界与反直觉
for...in会遍历可枚举的链上属性;Object.keys只看自身。Object.create(null)创建无原型对象,适合纯字典,但没有toString。- 改内置
prototype是高风险全局行为。
常见误区
[!warning] 常见误区:原型链 = 类的父类字段复制 没有在实例上复制方法表;是链接查找。
[!warning] 常见误区:arrow 方法挂原型一个样 类字段箭头函数常装在实例上,不共享;与原型方法不同。
工程实践
- 优先
class语法,但用原型心智调试。 - 字典数据用
Map或Object.create(null),避免踩Object.prototype键。 - 不要在热路径上深层原型上挂过多可枚举垃圾属性。
本节总结
原型链是属性查找的链表。会画链,才能理解继承、new、class 与方法共享。
自测题
a.formatStock()时方法在原型,this 是谁?- 为什么修改
SkuView.prototype.x会影响已有实例读取?
参考答案
- 一般是
a(接收者),由调用形式决定。 - 实例通过链接读到同一原型对象;原型被改,查找结果变。
延伸阅读
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| ECMA-262 | 规范 | 对象内部方法 |
| MDN Inheritance and prototype chain | 文档 | 原型链 |