原型 Prototype
基本定义
原型 Prototype 是 JavaScript 实现对象之间 属性/方法共享 的机制。
JavaScript 中的普通对象在创建时,内部都会有一个隐藏的链接,ECMAScript 规范中的普通对象(ordinary object)具有 [[Prototype]] 内部槽,指向另一个对象。
这个被指向的对象,就叫原型 Prototype。
Object.getPrototypeOf() 静态方法返回指定对象的原型(内部 [[Prototype]] 属性的值),
Array.prototype 示例
例如:
const arr = [1, 2, 3];
// true
Object.getPrototypeOf(arr) === Array.prototypearr 是一个数组对象,背后关联的原型是 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__ 访问器也可以观察或修改原型关系,但不建议在现代代码中使用。
当访问一个对象的属性或者方法时:
- 先在对象自身找;
- 找不到就去改对象的
[[Prototype]]找; - 还找不到就继续往上找,引擎就沿着这个链接往上找,直到找到为止;
- 或者到达链的终点
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 这些函数。
- arr 是一个数组实例
- 它的 [[Prototype]] 指向 Array.prototype
- Array.prototype 上真正存放着 map、filter、includes、push 等方法
- 当写 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- 函数首先是对象,可以有属性、可以被赋值、可以作为参数传递。
- 它额外多了一个能力:可以被调用(
[[Call]]内部方法)。 - 可以作为构造器使用的普通函数通常具有
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]]。