❓ 原型链是什么?
每个JavaScript对象都有一个原型对象,通过__proto__属性指向它的原型,而原型本身也是一个对象,它也可能有自己的原型,这样就形成了一个链式结构,直到某个对象的原型为null为止。比如:Object.prototype.__proto__为null。
🔍相关定义
-
prototype- 定义:只有函数拥有的属性,指向一个对象。该对象是构造函数创建的实例的原型(即实例继承属性和方法的来源)。
- 🌰示例:
Dog.prototype定义了所有由new Dog()创建的实例的共享属性和方法。
-
[[Prototype]]- 定义:是所有对象(包括函数、实例等)的内部隐藏属性,指向构造函数的
prototype(即继承的来源)。 - 🌰示例:
myDog.[[Prototype]]指向Dog.prototype(实际通过__proto__或者Object.getPrototypeOf())访问。
- 定义:是所有对象(包括函数、实例等)的内部隐藏属性,指向构造函数的
-
__proto__(实际为[[Prototype]]属性的访问器)- 定义:所有对象(包括实例和函数)的内部属性
[[Prototype]]的非标准访问方式,指向该对象的原型(即构造函数的prototype)。 - 🌰示例:
myDog.__proto__ === Dog.prototype。
- 定义:所有对象(包括实例和函数)的内部属性
🔍 prototype vs [[Prototype]]
| 特性 | prototype | [[Prototype]] |
|---|---|---|
| 所属对象 | 仅函数对象(构造函数)拥有 | 所有对象(包括函数、实例等)拥有 |
| 用途 | 定义实例的共享属性和方法 | 实现继承时查找属性的原型链指针 |
| 访问方式 | 显式属性(如 Dog.prototype) | 内部属性,需通过 __proto__ 或 Object.getPrototypeOf() 访问 |
| 是否可修改 | 可直接修改(如 Dog.prototype = {}) | 可通过 Object.setPrototypeOf() 修改,但性能不推荐 |
🌟 单链表与原型链
原型链本质上是一种单向链表,而链表的终点需要一个明确的终止符。在计算机科学中,链表通常以 null(或空指针)结尾,以防止无限遍历。原型链的设计完全遵循这一原则:
| 数据结构特性 | 原型链实现 |
|---|---|
| 节点(Node) | 对象(如 obj、Foo.prototype) |
| 指针(Pointer) | [[Prototype]](即 __proto__) |
| 终止符(Sentinel) | null |
🎯 构造函数、实例、原型的关系
function Dog() {} // 构造函数
const myDog = new Dog(); // 实例
myDog.__proto__ === Dog.prototype; // true
🌰 举个例子:
1. 核心角色设定
- 构造函数(掌门) :负责收徒传功,自带「门派秘籍」(
prototype属性)。 - 实例对象(徒弟) :通过
new拜师,获得「师父联系方式」(__proto__)。 - 原型对象(秘籍) :存储门派共享功法(
prototype指向的对象)。
// 开宗立派:创建一个构造函数(掌门)
function 武当派(弟子名) {
this.道号 = 弟子名;
}
// 编写门派秘籍(prototype)
武当派.prototype.内功 = "太极心法";
武当派.prototype.招式 = function() {
console.log(this.道号 + "施展了太极拳!");
};
// 收徒(实例化)
const 张三 = new 武当派("张翠山");
-
秘籍传递规则:
- 徒弟的
__proto__= 掌门的prototype - 掌门的
prototype本身也是个对象,它的__proto__指向更古老的秘籍(Object.prototype) - 终极秘籍的
__proto__是null(武学源头不可考)
- 徒弟的
3. 功法调用流程(比试现场)
console.log(张三.道号); // "张翠山"(直接获取)
console.log(张三.内功); // "太极心法"(查秘籍)
张三.招式(); // "张翠山施展了太极拳!"(查秘籍)
// 掌门偷偷更新秘籍,所有徒弟自动升级!
武当派.prototype.轻功 = "梯云纵";
console.log(张三.轻功); // "梯云纵"(动态继承)
4. 叛出师门(修改原型链)
// 张三偷学明教功法(修改 __proto__)
const 明教秘籍 = { 内功: "乾坤大挪移", 招式: function() { console.log("圣火令武功!"); } };
张三.__proto__ = 明教秘籍;
console.log(张三.内功); // "乾坤大挪移"(已叛变)
张三.招式(); // "圣火令武功!"
console.log(张三.轻功); // undefined(原门派的升级失效)
5. 江湖辈分验证(instanceof 原理)
console.log(张三 instanceof 武当派); // false(已修改 __proto__)
console.log(明教秘籍.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null(武学尽头)
6.关键知识点总结
| 概念 | 比喻 | 代码表现 | 核心作用 |
|---|---|---|---|
prototype | 掌门编写的门派秘籍 | 构造函数.prototype | 定义实例共享的属性和方法 |
__proto__ | 徒弟掌握的师父联系方式 | 实例.__proto__ | 实现原型链查找的指针 |
new 操作 | 拜师仪式 | const 徒弟 = new 掌门() | 绑定 proto 到 prototype |
| 原型链终点 | 武学源头失传 | Object.prototype.__proto__ = null | 终止查找,防止死循环 |
7.一句话理解 prototype 和 __proto__
prototype是师父的教案(只有函数有):定义徒弟能学什么__proto__是徒弟的笔记(所有对象有):记录实际跟谁学的new操作是拜师仪式:把徒弟的笔记(__proto__)指向师父的教案(prototype)
8.冷知识:为什么不能「我师父的师父是我」?
如果试图制造循环链:
武当派.prototype.__proto__ = 张三; // 试图让秘籍指向徒弟
JS 引擎会直接拒绝并报错:TypeError: Cyclic __proto__ value
—— 江湖规矩:辈分不能乱!
💡 Function 与 Object 的“鸡蛋理论”问题
在 JavaScript 中,Function 和 Object 的原型链确实存在一个经典的“鸡生蛋还是蛋生鸡”的循环依赖问题。这个问题源于它们的原型链相互指向对方,形成了一个闭环,但 JavaScript 引擎通过内部机制解决了这一矛盾。
1. Function 与 Object 的相互依赖
-
Object 是 Function 的实例
Object构造函数本身是一个函数,因此它是Function的实例:console.log(Object instanceof Function); // true console.log(Object.__proto__ === Function.prototype); // true -
Function 是 Object 的实例
Function构造函数本身也是一个对象,因此它继承自Object:console.log(Function instanceof Object); // true console.log(Function.__proto__.__proto__ === Object.prototype); // true
2. Function.prototype 与 Object.prototype
-
Function.prototype 是一个对象
Function.prototype本身是一个空函数,但它继承自Object.prototype:console.log(Function.prototype.__proto__ === Object.prototype); // true -
Object.prototype 是原型链的终点
Object.prototype的原型是null,终结了原型链:console.log(Object.prototype.__proto__); // null
3. 循环依赖的关键路径
-
Object → Function.prototype → Object.prototype
Object是Function的实例,因此Object.__proto__ === Function.prototype。Function.prototype是对象,因此Function.prototype.__proto__ === Object.prototype。
-
Function → Function.prototype → Object.prototype
Function是自身的实例(Function.__proto__ === Function.prototype),但最终也继承自Object.prototype。
4. 为什么没有矛盾?
JavaScript 引擎在底层通过初始化顺序和特殊处理解决了这一问题:
-
初始化顺序:
- 引擎首先创建
Object.prototype(原型链终点)。 - 然后创建
Function.prototype,并让它继承Object.prototype。 - 最后创建
Function和Object构造函数,并让它们继承Function.prototype。
- 引擎首先创建
-
特殊处理:
Function.prototype被设计为一个空函数对象,既是函数又是对象,作为原型链的桥梁。Function和Object的__proto__被硬编码指向Function.prototype,形成闭环。