Skip to content

🔗 原型和原型链

zc0kUP.png

❓ 为什么会产生原型和原型链?

理解一个机制最好的方式是回到它诞生的那一刻。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)写死的内存布局

⛓️ 物理上的“协议绑定”

引擎启动时,直接在内存地址上进行了以下“暴力”赋值:

  1. 创建 Object.prototype:它是万物的终点,其 __proto__null:flag_finish:。
  2. 创建 Function.prototype:强制设置 Function.prototype.__proto__ = Object.prototype(因为仓库也是对象)🔗。
  3. 身份授予
    • 强制 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 时,主线程会执行以下递归逻辑:

  1. 检查自身:如果 obj 自身有属性 a,返回之。
  2. 沿着“梯子”向上爬:如果没有,顺着 obj.__proto__ 爬到父辈的 prototype 仓库找。
  3. 终点止步:一直爬到 Object.prototype。如果还没找到,返回 undefined

⚠️ 避坑与性能

🛡️ Object.create(null)

这是创建一个“法外之地”对象的方法。它没有 __proto__,不继承 Object.prototype

  • 用途:作为极度纯净的数据字典,防止“原型污染”攻击。

🚩 属性屏蔽 (Shadowing)

当你在实例上设置一个与原型同名的属性时,它不会修改原型。它只是在查找链的第一层覆盖了原型属性。

🐇 性能陷阱

  • 严禁修改 __proto__:使用 Object.setPrototypeOf() 修改已创建对象的原型会导致 V8 引擎的 JIT(即时编译)优化失效,性能可能暴跌💥。
  • 查找成本:过长的原型链(深层继承)会导致属性查找变慢🐢。

🤔 总结

  1. __proto__指向其构造函数的原型(prototype
  2. StringArrayNumberFunctionObject等都是 function,其构造函数都是Function
  3. 由1,2可知,StringArrayNumber等的__proto__都指向 Function.prototype
  4. Function__proto__指向其自身的prototype
  5. Function.prototype__proto__指向Object.prototype
  6. Object.prototye__proto__指向顶端null
  7. __proto__ 向上找'爸爸',prototype 向下留家产。