❓ 原型链是什么?
每个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
,形成闭环。