深入理解 JavaScript 原型与原型链:从关系图到核心逻辑
在 JavaScript 世界里,原型(Prototype)和原型链(Prototype Chain) 是理解对象继承、属性查找机制的基石。很多开发者初学时对它们 “又爱又恨”,这篇文章将结合经典关系图,用通俗易懂的方式拆解原型与原型链的核心逻辑,帮你彻底掌握这套机制!
一、先搞懂几个核心概念
在分析关系图前,先明确 JavaScript 中与原型相关的关键概念,避免后续混淆:
1. 函数对象与普通对象
- 函数对象:由
Function
构造出来的对象,本质是可执行的函数(比如function Foo(){}
、内置的Object()
、Function()
)。 - 普通对象:由构造函数(如
new Foo()
、new Object()
)创建的实例,或直接字面量{}
创建的对象。
关键区别:函数对象有 prototype
属性(用于关联原型对象),普通对象没有 prototype
,但所有对象(包括函数对象)都有 __proto__
属性(指向自身的原型)。
2. prototype
vs __proto__
vs constructor
prototype
:函数对象专属的属性,指向一个 “原型对象”。当用这个函数作为构造函数创建实例时,实例的__proto__
会指向该prototype
。__proto__
:所有对象(包括函数) 都有的隐式原型属性,指向当前对象的 “原型对象”,是 JavaScript 引擎实现原型链查找的关键。constructor
:原型对象 上的属性,指向创建该原型的 “构造函数”(形成循环引用,方便实例找到构造函数)。
二、拆解关系图:原型与原型链的核心链路
结合题目中的关系图,我们从 构造函数、实例对象、原型对象 三个维度,梳理它们的关联:
1. 自定义构造函数 Foo
的链路
假设我们写了一个构造函数 function Foo() {}
,它的原型链路是这样的:
(1)构造函数 Foo
自身
Foo
是函数对象,由Function
构造而来(Foo = new Function(...)
的简化逻辑 )。- 所以:
Foo.__proto__
→ 指向Function.prototype
(函数对象的原型是Function
的原型 )。Foo.prototype
→ 指向Foo
的 “原型对象”(Foo.prototype
是普通对象,默认包含constructor: Foo
)。
(2)Foo
的实例(f1
、f2
)
用 new Foo()
创建实例 f1
、f2
时:
- 实例的
__proto__
→ 指向Foo.prototype
(构造函数的prototype
成为实例的原型 )。 - 所以:
f1.__proto__ === Foo.prototype
、f2.__proto__ === Foo.prototype
。
(3)Foo.prototype
的链路
Foo.prototype
是普通对象,它的 __proto__
指向谁?
- 因为所有普通对象默认由
Object
构造,所以:Foo.prototype.__proto__
→ 指向Object.prototype
(普通对象的原型链路起点 )。
2. 内置构造函数 Object
的链路
Object
是 JavaScript 内置的构造函数,用来创建普通对象(如 new Object()
、{}
),它的链路:
(1)构造函数 Object
自身
Object
是函数对象,同样由Function
构造而来(Object = new Function(...)
逻辑 )。- 所以:
Object.__proto__
→ 指向Function.prototype
(函数对象的原型统一关联Function.prototype
)。Object.prototype
→ 指向Object
的 “原型对象”(所有普通对象的最终原型之一 )。
(2)Object
的实例(o1
、o2
)
用 new Object()
创建实例 o1
、o2
时:
- 实例的
__proto__
→ 指向Object.prototype
(构造函数Object
的prototype
是实例原型 )。
(3)Object.prototype
的链路
Object.prototype
是 JavaScript 原型链的终点之一(最顶层普通对象原型 ),所以:Object.prototype.__proto__
→ 指向 null
(没有更上层的原型了 )。
3. 万物之源 Function
的链路
Function
是 JavaScript 中最特殊的构造函数,所有函数对象(包括 Object
、Foo
、Function
自身)都由它构造,链路非常 “递归”:
(1)构造函数 Function
自身
Function
是函数对象,同时它也是自身的实例(Function = new Function(...)
,自己构造自己 )。- 所以:
Function.__proto__
→ 指向Function.prototype
(自己的原型指向自己的prototype
,形成递归 )。Function.prototype
→ 指向Function
的 “原型对象”,它的__proto__
又指向Object.prototype
(因为Function.prototype
本质是普通对象 )。
(2)Function.prototype
的链路
Function.prototype
是函数对象的原型,它的 __proto__
:Function.prototype.__proto__
→ 指向 Object.prototype
(因为 Function.prototype
是普通对象,最终归属 Object
原型链 )。
三、原型链的本质:属性查找的 “追溯链”
理解了原型关联后,原型链 的作用就清晰了:
当你访问一个对象的属性(如 f1.name
)时,JavaScript 引擎会:
- 先在对象自身找(
f1
有没有name
属性 ); - 如果没找到,就顺着
__proto__
去原型对象里找(f1.__proto__
即Foo.prototype
里找 ); - 如果还没找到,继续顺着
Foo.prototype.__proto__
去Object.prototype
里找; - 直到找到属性,或追到
__proto__
为null
(原型链终点 ),返回undefined
。
举个例子:
js
function Foo() {}
Foo.prototype.sayHi = function() { console.log('Hi~'); };
const f1 = new Foo();
f1.toString(); // 能调用,因为:
// f1 自身没有 toString → 去 f1.__proto__(Foo.prototype)找 → 没有 → 去 Foo.prototype.__proto__(Object.prototype)找 → 找到 Object.prototype.toString
四、原型与原型链的实际应用
理解这套机制后,就能明白 JavaScript 中 “继承”“属性复用” 的底层逻辑,典型场景:
1. 原型继承
通过修改 __proto__
或利用 prototype
,让对象继承其他对象的属性:
js
const parent = { name: 'Parent' };
const child = { age: 18 };
child.__proto__ = parent;
console.log(child.name); // 从 parent 继承,输出 'Parent'
2. 构造函数复用方法
把方法挂载到 prototype
上,所有实例共享方法(节省内存):
js
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
const p1 = new Person('Alice');
const p2 = new Person('Bob');
p1.sayName(); // Alice(p1 自身无 sayName,去 Person.prototype 找 )
p2.sayName(); // Bob
3. 理解内置对象的原型
比如数组 []
的原型链:
js
const arr = [1, 2];
// arr.__proto__ → Array.prototype
// Array.prototype.__proto__ → Object.prototype
// 所以 arr 能调用 Array.prototype 的方法(如 push),也能调用 Object.prototype 的方法(如 toString)
五、总结:原型与原型链的核心逻辑
- 所有对象(包括函数) 都有
__proto__
,指向自己的原型; - 函数对象 额外有
prototype
,用于关联实例的__proto__
; - 原型链 是属性查找的链路,从自身到
__proto__
层层追溯,直到null
; Function
是 “函数对象的源头”,Object.prototype
是 “普通对象原型链的终点”,共同构成 JavaScript 对象体系的基石。