JavaScript原型与原型链深度解析:从构造函数到Function对象
本文深入解析JavaScript原型与原型链机制。通过对比传统构造函数的问题,阐述原型对象在共享方法、优化内存中的作用。详细讲解constructor、__proto__及[[Prototype]]内部属性的用法,剖析默认原型链结构。最后探讨Function构造函数动态生成函数的原理及其独特的原型链继承关系,帮助开发者理解JS面向对象的核心逻辑。
本文通过具体实例,详细解析JavaScript中原型与原型链的实际应用方法。
简而言之,原型是一个JavaScript对象,用于存储方法。由构造函数创建的实例对象可以引用并访问原型中定义的方法。
一、传统构造函数存在的问题
参考以下代码示例:
function Foo(){
this.sayHello = function(){
}
}
由于对象是通过调用new Foo()创建的,因此在每次创建对象时,函数sayHello都会被重新定义一次。
这导致每个对象都拥有独立但功能逻辑完全相同的函数副本,例如:{} == {}。
在代码中为每个对象重复定义方法会消耗性能,最显著的资源浪费体现在内存占用上。
最佳的解决方案是将函数定义在构造函数外部,然后在构造函数内部引用该函数。
function sayHello () {}
function Foo () {
this.say = sayHello;
}
这种方式在开发中可能带来维护困难:引入外部框架存在风险,且代码变得冗长难以维护。解决思路是确保外部函数不占用构造函数命名空间,并在函数名下进行组织。
每个函数在定义时,系统会自动创建一个神秘对象(即原型对象,暂且如此称呼)。
所有由该构造函数创建的对象,默认都会链接到这个神秘对象上。
var f1 = new Foo();
var f2 = new Foo();
f1.sayHello(); //如果f1没有sayHello那么就会在Foo.prototype中去找
由同一构造函数创建的多个对象共享同一个对象,即:构造函数.prototype。
只需将共享的内容、重复定义会占用额外内存的部分放入构造函数.prototype中,所有对象即可共享这些资源。
function Foo(){}
Foo.prototype.sayHello = function(){
console.log("….");
}
var f1 = new Foo();
f1.sayHello();
var f2 = new Foo();
f2.sayHello();
console.log(f1.sayHello === f2.sayHello); // true
二、相关核心概念解析
类(class):在JavaScript中,类通常对应于构造函数。
- 在传统面向对象语言中,使用“类”来定义模板,再通过模板创建对象。
- 在JavaScript的构造方法中也具备类似功能,因此常将其称为类。
实例(instance)与对象(object)的区别与联系
- 实例通常指由特定构造函数创建出来的对象,我们称之为“XXXX构造函数的实例”。
- 实例本质上就是对象。“对象”是一个更泛化的称呼。
- 实例与对象在概念上属于近义词。
键值对与属性和方法的关系
- 在JavaScript中,由键值对组成的集合被称为对象。
- 如果键对应的值是数据(非函数),则该键值对称为属性。
- 如果键对应的值是函数(方法),则该键值对称为方法(method)。
父类与子类(基类和派生类)的概念
- 在传统面向对象语言中,使用类来实现继承,从而产生父类和子类的概念。
- 父类也被称为基类,子类也被称为派生类。
- JavaScript中没有严格的“类”概念,通常称为父对象、子对象、基对象或派生对象。
三、深入认识原型
在JavaScript中,原型也是一个对象。通过原型可以实现对象的属性继承。JavaScript的每个对象内部都包含一个[[Prototype]]内部属性,该属性指向的就是该对象的原型。
[[Prototype]]作为对象的内部属性,不能直接访问。为了方便查看对象的原型,Firefox和Chrome提供了__proto__这个非标准(并非所有浏览器都支持)的访问器(ECMA标准引入了标准对象原型访问器“Object.getPrototypeOf(object)”)。
下面通过一个具体例子来理解原型相关概念:
function Person() {}
// 神秘对象就是Person.prototype
//那么只有使用构造函数才可以访问它
var o = new Person();
//以前不能直接使用o来访问神秘对象
//现在有了__proto__后,
o.__proto__也可以直接访问神秘对象
//那么o.__proto__ === Person.prototype
神秘对象(原型)中包含一个属性constructor,翻译为“构造器”。它表示该原型是与哪个构造函数相关联的。
__proto__有什么用?它可以用于访问原型。虽然在开发中除非有特殊要求,否则不建议通过实例直接修改原型的成员,导致该属性在开发中使用较少。但在调试过程中非常有用,可以轻易访问原型以查看其成员。
如果在早期浏览器中需要通过实例访问原型,该如何处理?可以使用实例对象访问构造器,然后通过构造器访问原型。
var o = new Person();
o.constructor.prototype
如果给实例继承自原型的属性赋值,会发生什么?
function Foo();
Foo.prototype.name = "test";
var o1 = new Foo();
var o2 = new Foo();
o1.name = "张三"; // 不是修改原型中的name而是自己增加了一个name属性
console.log(o1.name + ','+ o2.name); // 张三,test
四、构造、原型、实例的三角结构图
对于如下代码:
function Person(){}
var p = new Person()
console.log(Person.prototype.constructor); //function Person(){}
console.log(Person.prototype.constructor.name); //Person
console.log(typeof Person.prototype.constructor); //function
console.log(p.__prop__);
console.log(p.__prop__ === Person.prototype);//true
它们的关系图如下所示:

五、对象的原型链机制
凡是对象都有原型,而原型本身也是对象。因此,给定一个对象,就能找到它的原型;原型还有自己的原型,如此下去,就构成了一个对象的序列,这种结构称为原型链。
常见问题:
- 原型链到底以什么为终点?
- 一个默认的原型链结构是怎样的?
- 原型链结构对已知语法的修正。
5.1 原型链的结构详解
凡是使用构造函数创建对象,且没有利用赋值方式修改原型,该对象就保留了默认的原型链。
默认原型链结构具体是什么样子的?
function Person(){}
var p = new Person();
//p 具有默认的原型链
默认的原型链结构即为:当前对象 -> 构造函数.prototype -> Object.prototype -> null。

在实现继承时,有时会利用替换原型链结构的方式来实现原型继承,此时原型链结构会发生变化。
function DunizbCollection(){}
DunizbCollection.prototype = [];
var arr = new DunizbCollection();
// arr -> [] -> Array.prototype -> Object.prototype -> null

六、函数的构造函数Function
在JavaScript中,可以使用Function来实例化函数对象。也就是说,在JavaScript中,函数与普通对象一样,也是一种对象类型。函数是JavaScript中的一等公民。
- 函数是对象,因此可以使用对象的动态特性。
- 函数是对象,因此有构造函数来创建函数。
- 函数是对象,可以创建其他对象。
- 函数是唯一可以限定变量作用域的结果。
需要解决的关键问题:
- Function如何使用?
- Function与函数的关系。
- 函数的原型链结构。
6.1 函数是Function的实例
语法如下:
new Function( arg0,arg1,arg1,….argN, body );
Function中的参数全部是字符串。
该构造函数的作用是将参数链接起来组成函数。
- 如果只有一个参数,则表示函数体。
- 如果有多个参数,最后一个参数表示函数体,前面的所有参数表示函数的参数。
- 如果没有参数,表示创建一个空函数。
举例:创建一个打印一句话的函数。
// 传统的
function foo () {
console.log( '你好' );
}
//Function
var func = new Function( 'console.log( "你好" );' );
// 功能上,这里foo 与 func 等价
再比如,创建一个空函数。
//传统
function foo () {}
//Function
var func = new Function();
func();
传入函数内一个数字,打印该函数。
//传统
function foo ( num ) {
console.log( num );
}
//Function
var func = new Function( "num" ,"console.log( num )" );
func();
6.2 函数的原型链结构
任意一个函数,都相当于Function的实例,类似于{}与new Object()的关系。
function foo () {}
上面的代码告诉解析器,有一个对象叫foo,它是一个函数;相当于new Function()得到一个函数对象。
- 函数应该具备什么属性?答:
__proto__。 - 函数的构造函数是什么?答:Function。
- 函数应该继承自Function.prototype。
- Function.prototype继承自Object.prototype。
对于Function,我们还需要知道以下几点:
- Object函数是Function的一个实例。
-
Object作为对象是继承自Function.prototype的,而“Function.prototype”又继承自Object.prototype。
foo.prototype.__proto__ === Object.prototype // true - Function是自己的构造函数。
- 在JavaScript中,任何对象的老祖宗就是Object.prototype。
- 在JavaScript中,任何函数的老祖宗就是Function.prototype。
下面绘制出Function的构造、原型、实例三角形结构。

6.3 为什么要使用Function?
Function是通过字符串构建函数,因此可以在程序运行过程中动态构建函数。
以前的函数必须一开始就写好,再经过预解析,一步一步地运行。
假定从服务器拿到“[1,2,3,4,5]”,需要将数组形式的字符串转换成数组对象。
var arr = ( new Function( 'return ' + str + ' ;' ) )();
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容可查看本站专题:《JavaScript常用函数技巧汇总》、《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》及《JavaScript数学运算用法总结》。
希望本文所述对大家JavaScript程序设计有所帮助。
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
JS对象数组转字符串为什么推荐用JSON.stringify()
2026-11-05 09:40:31
