~/ ?.log $
返回文章列表
8 min read
更新于 2026年3月4日

我不知道的 V8(06)— prototype 和 __proto__ 的本质区别

prototype 和 proto 的混淆是 JavaScript 原型系统里最常见的误解之一。很多人知道"它们都和原型有关",但说不清楚谁属于谁、谁在什么时候起作用。这两个属性在 V8 内部分属不同的数据结构,服务于不同的目的——厘清这一点,原型链就从"需要记忆的规则"变成了…

prototype__proto__ 的混淆是 JavaScript 原型系统里最常见的误解之一。很多人知道”它们都和原型有关”,但说不清楚谁属于谁、谁在什么时候起作用。这两个属性在 V8 内部分属不同的数据结构,服务于不同的目的——厘清这一点,原型链就从”需要记忆的规则”变成了”自然推论”。

一、基本事实

prototype:只属于函数,是构造模板

function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function () {
  return `Hello, ${this.name}!`;
};

console.log(typeof Person.prototype); // "object"

每个函数创建时,V8 自动为它生成一个 prototype 属性,指向一个普通对象(默认只有 constructor 属性)。这个对象是”模板”:通过 new 创建的实例,会以它为原型。

__proto__:属于所有对象,是原型链接

const p = new Person('V8');
console.log(p.__proto__ === Person.prototype); // true

__proto__ 是每个对象内部的 [[Prototype]] 槽位,指向该对象的原型。查找属性时,V8 沿着 __proto__ 形成的链条向上查找,直到 null

二、V8 内部结构

两者在 V8 的 C++ 层有不同的归属:

  • prototype 存储在 JSFunction 对象的属性槽中,是函数对象的普通属性。修改 prototype 只影响后续通过这个构造函数创建的实例。

  • __proto__JSObject 的内部槽([[Prototype]]),不是普通属性。JavaScript 层通过访问器(getter/setter)暴露它,底层由 C++ 直接管理。推荐使用 Object.getPrototypeOf() 替代 __proto__(更符合规范,更明确):

const p = new Person('V8');
Object.getPrototypeOf(p) === Person.prototype; // true

三、new 操作符做了什么

new 是把 prototype__proto__ 联系起来的操作符。理解 new 的实现,两个属性的关系就清楚了:

// 手动实现 new 操作符的逻辑
function myNew(Constructor, ...args) {
  // 步骤 1:创建新对象,将 __proto__ 指向构造函数的 prototype
  const obj = Object.create(Constructor.prototype);

  // 步骤 2:执行构造函数,this 绑定到新对象
  const result = Constructor.apply(obj, args);

  // 步骤 3:如果构造函数返回了对象,使用该对象;否则使用 obj
  return typeof result === 'object' && result !== null ? result : obj;
}

// 验证:行为和 new 一致
const p1 = new Person('V8');
const p2 = myNew(Person, 'V8');

console.log(p1.sayHello()); // "Hello, V8!"
console.log(p2.sayHello()); // "Hello, V8!"
console.log(p1.__proto__ === Person.prototype); // true
console.log(p2.__proto__ === Person.prototype); // true

三个步骤对应的逻辑:

  1. Object.create(Constructor.prototype) → 创建一个以 prototype__proto__ 的新对象
  2. Constructor.apply(obj, args) → 运行构造函数,在新对象上设置实例属性
  3. 返回值处理 → 构造函数可以用返回值覆盖 new 创建的对象(下一节会解释这个设计)

四、构造函数返回值的特殊行为

大多数构造函数不显式返回值,new 的结果就是创建的那个对象。但如果构造函数显式返回一个对象,这个返回值会替代 new 通常创建的那个对象:

function SpecialFactory(name) {
  this.name = name;

  // 显式返回另一个对象
  return { specialName: name.toUpperCase() };
}

const s = new SpecialFactory('v8');
console.log(s.name); // undefined(不是 SpecialFactory 实例的属性)
console.log(s.specialName); // "V8"
console.log(s instanceof SpecialFactory); // false

规则:构造函数返回非原始类型时,new 的结果是这个返回值,而非内部创建的对象。返回原始类型(数字、字符串、布尔值)时忽略,还是用内部创建的对象。

这个特性在实际开发中有几个用途:

// 单例模式:确保只创建一个实例
let _instance = null;
function Singleton() {
  if (_instance) return _instance;
  _instance = this;
  this.id = Math.random();
}
const a = new Singleton();
const b = new Singleton();
console.log(a === b); // true

// 工厂模式:根据参数返回不同类型的实例
function UserFactory(type, data) {
  if (type === 'admin') return new AdminUser(data);
  return new RegularUser(data);
}

五、原型链的查找路径

属性查找沿 __proto__ 链逐级进行:

function Animal(type) {
  this.type = type;
}
Animal.prototype.describe = function () {
  return `This is a ${this.type}`;
};

const dog = new Animal('dog');
console.log(dog.describe()); // "This is a dog"

V8 查找 dog.describe 的路径:

1. dog 自身:有 type,没有 describe → 继续
2. dog.__proto__(= Animal.prototype):有 describe → 返回

内存布局:

Animal (JSFunction)
  └── prototype → { describe: [Function], constructor: Animal }
                    └── __proto__ → Object.prototype
                                       └── __proto__ → null

dog (JSObject)
  ├── type = "dog"(快属性,offset 0)
  └── __proto__ → Animal.prototype

每一级的查找,V8 都会检查是否命中内联缓存(IC)。如果 dog 和其他 Animal 实例都共享相同的隐藏类,IC 能直接缓存”在 Animal.prototype 的 offset N 找到 describe”这个结论,后续查找接近 O(1)。

六、常见误区

误区一:__proto__prototype 的别名

不是。修改函数的 prototype 不会改变已有实例的 __proto__

function Foo() {}
const f = new Foo();

Foo.prototype = { x: 1 }; // 修改 prototype,指向新对象

console.log(f.x); // undefined
// f.__proto__ 在 new 时就固定了,指向的是旧的 prototype 对象
// 修改 Foo.prototype 改变的是"新实例会以哪个对象为原型",不影响 f

误区二:普通对象有 prototype 属性

const obj = {};
console.log(obj.prototype); // undefined
console.log(obj.__proto__); // Object.prototype

prototype 是函数专属的。普通对象只有 __proto__(即 [[Prototype]]),默认指向 Object.prototype

误区三:直接修改 __proto__ 性能没影响

const p = new Person('V8');
p.__proto__ = { sayHello: () => 'Hi!' };
// V8 需要重新计算原型链,内联缓存失效
// 短期内对 p 的属性访问会变慢

需要改变原型链时,Object.create 在创建对象时设置原型,比后期修改 __proto__ 对 V8 更友好。

七、总结

prototype__proto__[[Prototype]]
归属只有函数对象有所有对象都有
作用new 时,新实例的 __proto__ 指向它原型链查找的跳转链接
修改影响影响后续创建的新实例影响当前对象的原型链和内联缓存
推荐访问方式直接 fn.prototypeObject.getPrototypeOf(obj)

prototype 是”构造函数提供的模板”,__proto__ 是”对象持有的对原型的引用”。new 把两者连接起来:新建对象 → 设置 __proto__ = 构造函数的 prototype → 运行构造函数。

理解这个连接关系,原型继承、instanceof 的判断逻辑、为什么修改 prototype 不影响已有实例——都可以从这里推导出来。


本系列其他文章:

share.ts

// 觉得这篇文章有帮助?

// 欢迎分享给更多人

export const  subscribe  =  "/rss.xml" ;