语言基础

发布于 2026-09-03

Prototype & Prototype Chain

理解 JavaScript 的原型与原型链,掌握 [[Prototype]]、prototype、属性查找、方法共享和原型继承等核心机制。

原型 Prototype

基本定义

原型 Prototype 是 JavaScript 实现对象之间 属性/方法共享 的机制。

JavaScript 中的普通对象在创建时,内部都会有一个隐藏的链接,ECMAScript 规范中的普通对象(ordinary object)具有 [[Prototype]] 内部槽,指向另一个对象。

这个被指向的对象,就叫原型 Prototype。

Object.getPrototypeOf() 静态方法返回指定对象的原型(内部 [[Prototype]] 属性的值),

Array.prototype 示例

例如:

const arr = [1, 2, 3];
 
// true
Object.getPrototypeOf(arr) === Array.prototype

arr 是一个数组对象,背后关联的原型是 Array.prototype 对象。

当调用 arr.map() 函数时,引擎会沿着原型链找到 Array.prototype.map 函数,这是原型继承 prototype inheritance最核心的机制。

1. arr 自己有 map 吗?
       ↓
      没有
 
2. arr 的原型 Array.prototype 有 map 吗?
       ↓
       有
 
3. 返回这个 map 函数

所以,当看到 MDN 关于标准内置对象文档时,会看到类似如下:

Array.prototype.map()
Array.prototype.filter()
Array.prototype.push()
Array.prototype.includes()

表示:Array.prototype 不是一种特殊语法,而是一个对象,实例数组调用的是原型的方法。

原型链 Prototype Chain

通过原型知道:每个 JavaScript 普通对象内部都有一个隐藏链接[[Prototype]],开发中应使用 Object.getPrototypeOf() 获取对象的原型。历史上常见的 __proto__ 访问器也可以观察或修改原型关系,但不建议在现代代码中使用。

当访问一个对象的属性或者方法时:

  1. 先在对象自身找;
  2. 找不到就去改对象的[[Prototype]]找;
  3. 还找不到就继续往上找,引擎就沿着这个链接往上找,直到找到为止;
  4. 或者到达链的终点 null 为止;

这条查找路径叫原型链 Prototype Chain。

对象自身直接拥有的属性称为 own property(自有属性);通过原型链访问到的属性称为 inherited property(继承属性)。

Property Lookup

Property Lookup——属性查找,是一套对象属性查找机制,“继承”是建立在这套机制之上的更高层表现。

属于委托式继承:对象通过一条隐藏的链接,委托(delegate)属性查找给它的原型。方法是共享的,不是复制的。调用时 this 会指向当前实例。

原型 + 原型链 = JavaScript 实现对象属性共享和方法复用的核心机制,是 ECMAScript 规范定义的对象模型的基础。属于语言最底层的设计之一。

比如:

Array.prototype 也是一个对象,既然是对象,也会有自己的原型:

Object.getPrototypeOf(Array.prototype) === Object.prototype
// 输出结果为 true

而 Object.prototype 的原型最终是 null:

Object.getPrototypeOf(Object.prototype)
// null

关系如下:

const arr = [1, 2, 3]
 
arr
 │
 │ [[Prototype]]
 ▼
Array.prototype
 │
 │ [[Prototype]]
 ▼
Object.prototype
 │
 │ [[Prototype]]
 ▼
null

还以 MDN 文档为例:

Array.prototype.map()

可以理解为:

map() 是定义在 Array.prototype 上的实例方法,因此 Array 创建出来的数组实例都可以使用它。

JavaScript 这门语言采用了基于原型的对象模型;ECMAScript 规范定义了相应行为;V8 负责高效实现这些行为。

ECMAScript 语言规范
    │
    │ 规定 JavaScript 应该有什么行为
    ▼
对象具有 [[Prototype]]
属性查找需要沿 Prototype Chain 进行
    │
    │
    ├── V8(Chrome / Node.js)
    ├── SpiderMonkey(Firefox)
    └── JavaScriptCore(Safari)
            │
            ▼
    各自实现这些规范要求

Prototype 和 [[Prototype]]

[[Prototype]]

  • 对象内部的原型关联。
  • 是规范里的内部槽(internal slot),不能把它简单理解成普通属性。

.prototype

  • 某些函数对象上的一个普通属性,比如:Array.prototype、Object.prototype等;
  • 是属性名。

原型链查找过程

手动模拟查找过程:

const arr = [1, 2, 3];
 
console.log(arr.map);               // 函数本身
console.log(arr.hasOwnProperty('map')); // false  → 自己没有
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
console.log(Array.prototype.hasOwnProperty('map')); // true  → 在这里找到了

一层一层往上找:

function printPrototypeChain(obj) {
  let current = obj;
  let level = 0;
 
  while (current !== null) {
    console.log(`第 ${level} 层:`, current.constructor?.name || current);
    // 看看当前层有没有 'map'
    if (current.hasOwnProperty && current.hasOwnProperty('map')) {
      console.log('  → 在这一层找到了 map!');
    }
    current = Object.getPrototypeOf(current);
    level++;
  }
  console.log('第', level, '层: null(链条结束)');
}
 
printPrototypeChain([1, 2, 3]);

自定义示例

const animal = {
  eat() {
    console.log('吃东西');
  }
};
 
const dog = Object.create(animal); // dog 的 [[Prototype]] 指向 animal
dog.bark = function () {
  console.log('汪汪');
};
 
dog.bark(); // 自己有
dog.eat();  // 自己没有 → 去原型 animal 上找 → 找到了
dog.toString(); // animal 也没有 → 再往上到 Object.prototype → 找到了

超找路径:

dog
  ↓ [[Prototype]]
animal          ← eat 在这里
  ↓ [[Prototype]]
Object.prototype ← toString 在这里
  ↓ [[Prototype]]
null

示例

const arr = [1, 2, 3];
 
arr.map(x => x * 2);
arr.filter(x => x > 1);
arr.includes(2);

arr 内部并没有保存 map、filter、includes 这些函数。

  1. arr 是一个数组实例
  2. 它的 [[Prototype]] 指向 Array.prototype
  3. Array.prototype 上真正存放着 map、filter、includes、push 等方法
  4. 当写 arr.map(...) 时,引擎沿着原型链找到了 Array.prototype.map,然后以 arr 作为 this 去调用它

可以在 Chrome Console 中验证:

const arr = [1, 2, 3];
 
console.log(arr.hasOwnProperty('map'));        // false
console.log(Array.prototype.hasOwnProperty('map')); // true
 
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

所有数组实例都通过原型链共享同一份方法。

arr
  ↓ [[Prototype]]
Array.prototype          ← map, filter, includes, push... 都在这里
  ↓ [[Prototype]]
Object.prototype         ← toString, hasOwnProperty, valueOf...
  ↓ [[Prototype]]
null                     ← 链条结束

原型链应用场景

场景说明
所有内置方法为什么能用arr.map、str.includes、obj.toString 都靠原型链
继承class Child extends Parent 底层就是在设置原型链
自己扩展内置对象(不推荐,但要知道)Array.prototype.myMethod = ...
判断类型 / 继承关系instanceof、isPrototypeOf
性能与内存方法共享,不会每个实例都复制一份
调试奇怪的行为属性怎么突然多出来了?可能是原型上的
框架 / 库的设计很多库用原型来做插件、混入(mixin)
  • 必须知道:方法不在实例上,而在原型上;this 指向谁。
  • 建议掌握:如何正确设置原型(Object.create、class)、instanceof 原理。
  • 深入了解:只有当写库、做框架、排查复杂继承问题、或对语言设计感兴趣时才需要。

补充:函数也是对象

在 JavaScript 里,函数就是一种特殊的对象。

function foo() {}
 
console.log(typeof foo);           // "function"(特殊的对象类型)
console.log(foo instanceof Object); // true
console.log(foo instanceof Function); // true
 
// 函数可以像普通对象一样加属性
foo.myProp = 123;
console.log(foo.myProp); // 123
 
// 函数也有原型
console.log(Object.getPrototypeOf(foo) === Function.prototype); // true
  1. 函数首先是对象,可以有属性、可以被赋值、可以作为参数传递。
  2. 它额外多了一个能力:可以被调用([[Call]] 内部方法)。
  3. 可以作为构造器使用的普通函数通常具有 prototype 属性。
    • 当通过 new Foo() 创建实例时,新对象的 [[Prototype]] 会关联到 Foo.prototype。

简化关系:

函数 foo
  ├── 自己是对象(可以挂属性)
  ├── [[Prototype]] → Function.prototype
  └── .prototype 属性 → 一个普通对象(给 new 出来的实例当原型用)

起源

JavaScript 从 1995 年诞生之初就采用了基于原型的对象模型,而不是传统的基于类的对象模型;这一设计后来被标准化进 ECMAScript。

后来这个设计被标准化进了 ECMAScript 规范(ECMA-262)。规范里正式定义了:

  • 普通对象都有 [[Prototype]] 内部槽
  • 属性访问算法([[Get]])会沿着原型链查找
  • Object.prototype 是几乎所有对象的最终原型
  • null 是原型链的终点

是 ECMAScript 语言规范明确设计的对象模型核心。

JavaScript 的对象模型就是围绕原型构建的。类(class)语法是后来加的语法糖,底层仍然是原型。

相关知识

  • 对象与属性:理解对象由一组属性(Property)组成,可以通过属性名读取值;
  • 函数:理解函数可以作为值使用,同时函数本身也是对象;
  • 引用:理解对象通过引用建立关系,而不是简单复制;
  • 属性访问:理解 obj.foo 和 obj["foo"] 都是在读取对象属性;
  • 内部槽:知道 ECMAScript 规范会使用 [[...]] 表示对象的内部机制,例如 [[Prototype]]。