在 JavaScript 中,对象属性的访问并非简单的键值对匹配。其背后隐藏着一套基于 [[Prototype]] 的委托机制。本文将跳过感性的类比,直接从 V8 引擎的属性查找原理 和 内存引用 出发,剖析原型系统的本质。
一、 显示原型(Explicit Prototype)
1. 属性定义与物理存储
当定义一个函数 function Foo() {} 时,V8 会在堆内存中自动创建一个对象,并让该函数的 prototype 属性指向该对象。
- 物理属性:prototype 是函数独有的属性。
- 用途:它定义了由该构造函数创建的所有实例共享的属性和方法。
function Foo(name) {
this.name = name; // 写入实例自身的存储空间
}
// 写入 Foo.prototype 存储空间,实现内存复用
Foo.prototype.walk = function() {
console.log(this.name + ' is walking');
};
2. 写入与读取权限
- 读取:实例可以读取 prototype 上的属性。
- 写入隔离:通过实例进行赋值操作(如 instance.prop = value)时,V8 只会在实例自身添加一个属性,而不会跨过原型去修改构造函数的 prototype。这种机制确保了原型属性的稳定性。
二、 隐式原型(Implicit Prototype)与 V8 查找机制
1. [[Prototype]] 内部槽
每个对象(除 null 外)在创建时都会关联另一个对象,这个对象就是它的原型。在规范中被称为 [[Prototype]],在浏览器实现中通常表现为 proto。
2. V8 属性查找算法
当访问 obj.prop 时,V8 的执行流程如下:
- Own Properties Check:检查当前对象内部的 properties 表(或常量池)中是否存在该键。
- Prototype Traversal:若未命中,V8 会获取 obj.proto 指向的地址,跳转到该对象进行查找。
- Recursion:重复步骤 2,直到找到匹配的键或到达链条终点 null。
3. 核心等式
当执行 const f = new Foo() 时,JavaScript 引擎会完成如下内存操作:
// 核心逻辑简述
f.__proto__ === Foo.prototype; // 指向同一内存地址
三、 原型链的完整链路推演
原型链的本质是对象引用组成的单向链表。
1. 实例链路分析
以 const f1 = new Foo() 为例,其内存搜索链路(Look-up Chain)如下:
- f1 的自有属性。
- f1.proto (对应 Foo.prototype)。
- f1.proto.proto (对应 Object.prototype)。
- f1.proto.proto.proto (结果为 null)。
2. 特殊实体:Function 与 Object
在 JavaScript 的底层实现中,函数也是对象。这意味着函数不仅有 prototype(用于它的实例),也有 proto(因为它自己也是别人的实例)。
// 函数作为对象,由 Function 构造
Foo.__proto__ === Function.prototype;
// Function 构造函数本身也是对象
// 这是一个自引用的特殊设计:Function 是由 Function 产生的
console.log(Function.__proto__ === Function.prototype); // true
// 最终所有原型对象(除了 Object.prototype)都源自 Object
console.log(Function.prototype.__proto__ === Object.prototype); // true
四、 总结:理解原型链的三个维度
- 构造维度:prototype 决定了子类“能继承什么”。
- 查找维度:proto 决定了对象“向上查找的路径”。
- 连接维度:constructor 建立了原型对象与构造函数的回溯关系。 底层意义:原型链通过内存引用而非内存拷贝实现了属性共享。这种“委托查找”模式是 JavaScript 实现高效面向对象编程的基础。
作者:周同学 笔记摘要:本文整理自个人关于 JavaScript 核心原理的 MD 笔记,旨在通过底层逻辑理解语言特性,欢迎同道中人交流。 #JavaScript #V8引擎 #原型链 #底层原理