深入理解 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 对象体系的基石。