JavaScript 原型与原型链
1. 什么是原型(Prototype)?
首先,我们使用构造函数创建一个对象:
function Person(name) {
this.name = name;
} // 构造函数 Person
const son = new Person('小号');
现在,我们通过构造函数创建了实例 son。在 JavaScript 中,这两个角色之间存在着一种特殊的“血缘关系”:
prototype:每一个函数在创建时,都会自动获得一个prototype属性,它指向一个对象。这个对象就是通过该构造函数创建的实例的“公共仓库”。__proto__:每一个实例对象(如son)都有一个__proto__属性,它也指向这个“公共仓库”。
我们可以通过代码验证它们指向的是同一个地方:
console.log(son.__proto__ === Person.prototype); // true
2. 原型的作用:共享与继承
我们创建的 son 本身只有一个 name 属性。如果我们想让所有 Person 的实例都拥有某种方法,如果直接写在构造函数里,每个实例都会在内存中复制一遍该方法,这会造成极大的内存浪费。
原型的初衷: 就是为了实现属性和方法的共享。
// 给原型添加方法
Person.prototype.sayHello = function() {
console.log(`你好,我是 ${this.name}`);
};
// 实例 son 本身没有 sayHello,但它可以直接调用
son.sayHello(); // 输出:你好,我是 小号
寻址机制: 当我们在访问一个实例的属性或方法时,JavaScript 引擎会执行以下逻辑:
- 首先在 实例对象自身 寻找,找到了就直接使用。
- 如果没找到,就会沿着
__proto__属性去它的 原型对象 中寻找。
3. 核心:构造函数、原型与实例的“三角关系”
为了完整理解原型,我们需要引入第三个关键属性:constructor。
- 原型对象 默认有一个
constructor属性,指向它关联的 构造函数。
| 属性 | 持有者 | 指向 | 作用 |
|---|---|---|---|
prototype | 构造函数 | 原型对象 | 存放共享资产 |
__proto__ | 实例对象 | 原型对象 | 向上找“祖先”的通路 |
constructor | 原型对象 | 构造函数 | 认祖归宗,找回谁创建了它 |
console.log(Person.prototype.constructor === Person); // true
4. 原型链(Prototype Chain)
我们已经知道了原型和它的作用,但故事还没结束。原型对象本质上也是一个对象,既然是对象,它就有自己的 __proto__,指向它自己的原型。
这种由 __proto__ 一层层往上连接形成的链式结构,就是 原型链。
// son 的原型是 Person.prototype
console.log(son.__proto__ === Person.prototype); // true
// Person.prototype 的原型是 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype); // true
// 原型链的尽头是 null
console.log(Object.prototype.__proto__); // null
5、原型链尽头
如果只需要了解原型与原型继承,上面的理解已经足够解释一些基础问题。但如果想深入了解原型,那我们就要进一步追问:原型的尽头是什么?
5.1 为什么尽头是 null?
在原型链中,当我们访问一个不存在的属性时,JS 引擎会沿着 __proto__ 向上查找。
- 逻辑闭环: 如果原型链是一个无限循环,查找操作将永不停止,导致浏览器崩溃。
- 虚无的定义:
null在 JavaScript 中代表“空”或“不存在”。将Object.prototype.__proto__指向null,相当于给引擎发出了一个显式的停止信号。
结论: 凡是顺着
__proto__爬升,最终都会汇聚到Object.prototype,而它背后的“墙”就是null。
5.2 构造函数的“双重身份”
这是理解原型链最绕、但也最精妙的地方:构造函数本身也是一个对象。
- 身为“造物主”:
Person有一个prototype属性,指向它为实例准备的“遗产仓库”。 - 身为“被造物”:
Person本身是由Function构造出来的。因此,Person.__proto__ === Function.prototype。
5.3 最初的对象
在 V8 或其他 JS 引擎启动时,它们会按照以下逻辑顺序构建内置对象:
-
第一步:创建
Object.prototype。它是万物之源,它的__proto__直接指向null。 -
第二步:创建
Function.prototype。它是一个对象,所以它的__proto__指向刚才创建好的Object.prototype。 -
第三步:创建内置构造函数
Function和Object。- 因为它们是函数,所以它们的
__proto__都指向第二步创建好的Function.prototype。 Object.prototype被赋值给Object.prototype属性。Function.prototype被赋值给Function.prototype属性
- 因为它们是函数,所以它们的