JavaScript 原型与原型链深度解析:从对象分类到内存模型
本文深入解析 JavaScript 的原型与原型链机制。首先区分普通对象与函数对象,阐述 prototype 与 __proto__ 的区别与联系。通过内存结构图解析原型链的形成原理,重点说明继承实际依赖 __proto__ 而非 prototype。最后探讨 constructor 属性及特殊对象 Function.prototype 的指向问题,帮助开发者彻底理解 JS 的面向对象模型。
JavaScript 原型及原型链终极详解
普通对象与函数对象的区分
一、普通对象与函数对象的区分
在 JavaScript 中,一切皆被视为对象,但对象内部存在显著差异,主要划分为普通对象和函数对象两类。其中,Object 和 Function 是 JavaScript 内置的函数对象。以下通过具体示例进行说明。
function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
var o3 = new f1();
var o1 = {};
var o2 =new Object();
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof o1); //object
console.log(typeof o2); //object
console.log(typeof o3); //object
console.log(typeof f1); //function
console.log(typeof f2); //function
console.log(typeof f3); //function
在上述示例中,o1、o2 和 o3 属于普通对象,而 f1、f2 和 f3 则属于函数对象。区分两者的标准十分明确:凡是通过 new Function() 构造函数创建的对象均为函数对象,其余情况创建的对象均为普通对象。f1 和 f2 本质上也是通过 new Function() 的方式创建的,而 Function 和 Object 同样是通过 New Function() 创建的。
原型对象的概念
二、原型对象的概念
在 JavaScript 中,每当定义一个对象(包括函数)时,该对象内部会自动包含一些预定义的属性。对于函数对象而言,其中一个关键属性是原型对象 prototype。需要注意的是:普通对象没有 prototype 属性,但拥有 __proto__ 属性。
原型对象本身通常是一个普通对象(Function.prototype 除外,它是一个特殊的函数对象,没有 prototype 属性,这与前文提到的函数对象拥有 prototype 属性相矛盾,属于特例)。参考以下示例。
function f1(){};
console.log(f1. prototype) //f1 {}
console.log(typeof f1. prototype) //Object
console.log(typeof Function. prototype) // Function
console.log(typeof Object. prototype) // Object
console.log(typeof Function. prototype. prototype) //undefined
从代码 console.log(f1.prototype) 输出 f1 {} 的结果可以看出,f1.prototype 实际上是 f1 的一个实例对象。即在 f1 创建时,系统创建了一个它的实例对象并将其赋值给它的 prototype,基本过程如下。
var temp = new f1();
f1. prototype = temp;
因此,Function.prototype 之所以是函数对象便得以解释:前文提到凡是由 new Function() 产生的对象都是函数对象,所以这里的 temp 变量指向的对象也是函数对象。
var temp = new Function ();
Function. prototype = temp;
那么,原型对象的主要作用是什么?其核心功能在于支持继承。以下通过具体示例进行说明。
var person = function(name){
this.name = name
};
person.prototype.getName = function(){
return this.name;
}
var zjh = new person(‘zhangjiahao');
zjh.getName(); //zhangjiahao
从该示例可以看出,通过给 person.prototype 设置一个函数对象属性,由 person 构造函数生成的实例(如示例中的 zjh)所对应的普通对象便继承了该属性。具体的继承实现机制涉及原型链,将在下文详细阐述。
原型链的形成机制
三、原型链的形成机制
JavaScript 在创建对象(无论是普通对象还是函数对象)时,都会内置一个名为 __proto__ 的属性,该属性用于指向创建它的函数对象的原型对象 prototype。以上述示例为例。
console.log(zjh.__proto__ === person.prototype) 的输出结果为 true。
同样,person.prototype 对象也拥有 __proto__ 属性,它指向创建它的函数对象(即 Object)的 prototype。
console.log(person.prototype.__proto__ === Object.prototype) 的输出结果为 true。
继续追溯,Object.prototype 对象也拥有 __proto__ 属性,但该属性比较特殊,其值为 null。
console.log(Object.prototype.__proto__) 的输出结果为 null。
我们将通过 __proto__ 属性串联起来,直到 Object.prototype.__proto__ 为 null 的这一链条称为原型链。具体结构如下图所示。

内存结构解析
四、内存结构解析
为了更深入且直观地理解上述机制,下面绘制相应的内存结构图。

绘图约定说明如下。

针对常见疑点的解释如下。
1. Object.__proto__ === Function.prototype 的结果为 true。
Object 是函数对象,它是通过 new Function() 创建的,因此 Object.__proto__ 指向 Function.prototype。
2. Function.__proto__ === Function.prototype 的结果为 true。
Function 也是函数对象,同样是通过 new Function() 创建的,因此 Function.__proto__ 指向 Function.prototype。
这种“自己由自己创建”的情况看似不符合逻辑,但仔细思考,现实世界也有类似现象:你是如何诞生的?由你母亲所生,你母亲又是如何诞生的?由你外祖母所生……追溯至类人猿进化而来,而类人猿从何而来?一直追溯下去……最终归于无(NULL 生万物)。
正如《道德经》所言:“无,名天地之始”。
3. Function.prototype.__proto__ === Object.prototype 的结果为 true。
这一点我也曾感到困惑,但可以尝试进行解释。
Function.prototype 是一个函数对象,理论上其 __proto__ 应该指向 Function.prototype 自身,即指向自己。然而,自己指向自己没有实际意义。
JavaScript 强调万物皆对象,函数对象也是对象,因此给它指定一个“祖先”,即指向 Object.prototype。由于 Object.prototype.__proto__ === null,这保证了原型链能够正常结束。
constructor 属性
五、constructor 属性
原型对象 prototype 中预定义了 constructor 属性,用于引用其对应的函数对象。这是一种循环引用关系。
person.prototype. constructor === person //true
Function.prototype.constructor === Function //true
Object.prototype.constructor === Object //true
完善后的内存结构图如下所示。

有两点需要特别注意。
1. 注意 Object.constructor === Function 的结果为 true,因为 Object 本身就是由 Function 构造函数创建的。 2. 查找一个对象的 constructor 时,需在该对象的原型链上寻找碰到的第一个 constructor 属性所指向的对象。
总结
六、总结
1. 原型和原型链是 JavaScript 实现继承的一种模型。
2. 原型链的真正形成依靠的是 __proto__,而非 prototype。
要深入理解这句话,我们再举一个例子,检验前文的理解是否到位。
var animal = function(){};
var dog = function(){};
animal.price = 2000;//
dog.prototype = animal;
var tidy = new dog();
console.log(dog. price) //undefined
console.log(tidy.price) // 2000
原因何在?绘制内存图如下。

这说明了什么问题?执行 dog.price 时,发现没有 price 属性,虽然 prototype 指向的 animal 拥有该属性,但 JavaScript 并未沿着“链”去寻找。同样,执行 tidy.price 时,也没有该属性,但 __proto__ 指向了 animal,它会沿着该链去寻找,由于 animal 中有 price 属性,因此 tidy.price 输出 2000。由此得出结论:原型链的真正形成依靠的是 __proto__,而不是 prototype。
因此,如果显式指定 dog.__proto__ = animal,那么 dog.price 将等于 2000。
1.
最后做一个比喻,虽然并不完全精确,但可能对理解原型有所帮助。

父亲(函数对象)先生了一个大儿子(prototype),也就是你的大哥。父亲给大哥买了很多玩具,当你出生时,你们之间的亲情纽带(__proto__)使你自然地拥有了大哥的玩具。同样,你也先生个大儿子,又给他买了很多玩具,当你再生儿子时,你的小儿子会自然拥有你大儿子的所有玩具。至于他们之间是否会打架,这已不在我们的讨论范围内。
因此,你是从你大哥那里继承的,这印证了那句“长兄如父”的说法。
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
