🔗 原型和原型链
❓ 为什么会产生原型和原型链?
理解一个机制最好的方式是回到它诞生的那一刻。1995 年,JavaScript 之父 Brendan Eich 在设计这门语言时,面临着特殊的历史局限和需求:
- 📝 内存资源的极致节省
- 👨👩👧 继承
- ⚡ 动态性的追求
📝 内存资源的极致节省
在 90 年代,计算机内存通常只有 8MB 或 16MB。
- 痛点:如果创建 1000 个“学生”对象,每个对象都有自己的
study方法,那么内存中就会存在 1000 个功能一模一样的函数副本🗑️。 - 方案:设计一个“公共仓库”(原型),让所有实例通过一个细小的指针(
__proto__)指向它🏠。 - 价值:实现属性与方法的共享🤝,极大降低内存占用📉。
👨👩👧 继承
当时网景公司要求 JS “长得像 Java”(面向对象),但不能像 Java 那么重(不需要复杂的类、接口、包)。
- 方案:借鉴 Self 语言的“原型继承”思想💡。
- 价值:对象直接继承对象🧬,省去了“类”的定义过程,让语言变得极其灵活:fluid_oz:。
⚡ 动态性的追求
JavaScript 是一门解释型脚本语言,需要支持运行时修改。
- 特性:由于原型链是实时查找的,即使对象已经创建,只要修改了原型,所有实例会立刻获得新能力(热插拔)🔌。
🔑 核心属性
要理清原型链,必须先分清三个属性:
| 属性 | 所属主体 | 身份类比 | 职责 |
|---|---|---|---|
prototype | 函数(Function) | 🏭 工厂的“模版仓库” | 规定生产出的产品(实例)共有的属性和方法。 |
__proto__ | 所有对象(Object) | 🛂 公民的“寻根护照” | 指向创建该对象的构造函数的 prototype,是查找的物理路径。 |
constructor | 原型对象(仓库) | 📜 仓库的“回执证明” | 指回该原型关联的构造函数。 |
🔨 底层真相
原型链最顶层的逻辑并不是由 JavaScript 产生的,而是 JavaScript 引擎(如 V8)在启动时通过 C++ 强行硬编码(Hard-coded)写死的内存布局。
⛓️ 物理上的“协议绑定”
引擎启动时,直接在内存地址上进行了以下“暴力”赋值:
- 创建
Object.prototype:它是万物的终点,其__proto__为null:flag_finish:。 - 创建
Function.prototype:强制设置Function.prototype.__proto__ = Object.prototype(因为仓库也是对象)🔗。 - 身份授予:
- 强制
Object.__proto__ = Function.prototype(因为 Object 是函数)📜。 - 强制
Function.__proto__ = Function.prototype(自产自销,保持闭环)♻️。
- 强制
♾️ 深度闭环:Function 与 Object 的关系
基于上述硬编码,我们得到了以下几个至高无上的等式,它们揭示了 JS “万物皆对象”与“函数一等公民”的并存:
所有的构造函数(包括 Object, Array)都是 Function 的实例:
Object.__proto__ === Function.prototype所有的原型对象(包括 Function.prototype)最终都是 Object 的实例:
Function.prototype.__proto__ === Object.prototype宇宙的尽头:
Object.prototype.__proto__ === null
Function 提供了“动”的能力(让 Object 能够调用)🏃,而 Object 提供了“静”的身体(为 Function 提供基础对象特征)。
🔍 原型链的属性搜索算法
当你访问 obj.a 时,主线程会执行以下递归逻辑:
- 检查自身:如果
obj自身有属性a,返回之。 - 沿着“梯子”向上爬:如果没有,顺着
obj.__proto__爬到父辈的prototype仓库找。 - 终点止步:一直爬到
Object.prototype。如果还没找到,返回undefined。
⚠️ 避坑与性能
🛡️ Object.create(null)
这是创建一个“法外之地”对象的方法。它没有 __proto__,不继承 Object.prototype。
- 用途:作为极度纯净的数据字典,防止“原型污染”攻击。
🚩 属性屏蔽 (Shadowing)
当你在实例上设置一个与原型同名的属性时,它不会修改原型。它只是在查找链的第一层覆盖了原型属性。
🐇 性能陷阱
- 严禁修改
__proto__:使用Object.setPrototypeOf()修改已创建对象的原型会导致 V8 引擎的 JIT(即时编译)优化失效,性能可能暴跌💥。 - 查找成本:过长的原型链(深层继承)会导致属性查找变慢🐢。
🤔 总结
__proto__指向其构造函数的原型(prototype)String、Array、Number、Function、Object等都是function,其构造函数都是Function- 由1,2可知,
String、Array、Number等的__proto__都指向Function.prototype Function的__proto__指向其自身的prototypeFunction.prototype的__proto__指向Object.prototypeObject.prototye的__proto__指向顶端null__proto__向上找'爸爸',prototype向下留家产。
@keyboarder-yang