深入理解 JavaScript prototype 原型链与对象共享机制
本文深入解析 JavaScript 中 prototype 的核心机制。通过对比私有、静态与实例变量,阐明 prototype 如何解决方法重复创建的性能问题。详细讲解原型链的搜索规则、__proto__ 内部指针的作用,以及利用 prototype 构建高复用率对象的最佳实践。
JavaScript prototype 核心机制
熟悉 JavaScript 的开发者对 prototype 一词并不陌生,但许多初学者往往只知其名不知其意,仅知道函数拥有 prototype 属性并可添加供实例访问的方法,对其内部机制缺乏深入了解。近期通过研读《JavaScript 高级程序设计》,终于理清了 prototype 的运作原理。
每个函数都具备一个 prototype 属性,该属性指向一个被称为原型对象的引用。原型对象中存放着由函数实例共享的方法和属性。当使用 new 操作符调用构造函数时,新创建的对象会自动从原型对象继承这些属性和方法。
私有变量与函数
私有变量、函数
在深入探讨 prototype 之前,先介绍几个相关概念,这有助于更好地理解 prototype 的设计初衷。在 JavaScript 的函数作用域中,如果在函数内部定义变量或函数且不对外提供访问接口,外部将无法访问它们,从而形成私有变量和私有函数。
代码如下:
function Obj(){
var a=0; //私有变量
var fn=function(){ //私有函数
}
}
这样在函数对象Obj外部无法访问变量a和函数fn,它们就变成私有的,只能在Obj内部使用,即使是函数Obj的实例仍然无法访问这些变量和函数
代码如下:
var o=new Obj();
console.log(o.a); //undefined
console.log(o.fn); //undefined
静态变量与函数
静态变量、函数
当定义一个函数后,通过 “.” 为其添加的属性和函数,可以通过对象本身访问,但其实例却无法访问。这类变量和函数分别被称为静态变量和静态函数,熟悉 Java 或 C# 的开发者对静态概念应比较容易理解。
代码如下:
function Obj(){
}
Obj.a=0; //静态变量
Obj.fn=function(){ //静态函数
}
console.log(Obj.a); //0
console.log(typeof Obj.fn); //function
var o=new Obj();
console.log(o.a); //undefined
console.log(typeof o.fn); //undefined
实例变量与函数
实例变量、函数
在面向对象编程中,除了库函数,我们通常希望在定义对象时同时定义一些属性和方法,以便实例化后访问。JavaScript 同样支持这种机制。
代码如下:
function Obj(){
this.a=[]; //实例变量
this.fn=function(){ //实例方法
}
}
console.log(typeof Obj.a); //undefined
console.log(typeof Obj.fn); //undefined
var o=new Obj();
console.log(typeof o.a); //object
console.log(typeof o.fn); //function
这样可以达到上述目的,然而
代码如下:
function Obj(){
this.a=[]; //实例变量
this.fn=function(){ //实例方法
}
}
var o1=new Obj();
o1.a.push(1);
o1.fn={};
console.log(o1.a); //[1]
console.log(typeof o1.fn); //object
var o2=new Obj();
console.log(o2.a); //[]
console.log(typeof o2.fn); //function
上述代码运行结果符合预期,但也揭示了一个问题:在 o1 中修改了 a 和 fn,而 o2 未受影响。由于数组和函数均为引用类型的对象,这说明 o1 和 o2 中的同名属性与方法并非指向同一引用,而是各自拥有对 Obj 定义内容的副本。
这种对属性而言影响不大,但对方法而言问题严重。因为所有方法执行的功能完全相同,却存在多份副本。若一个函数对象包含上千个实例方法,每个实例都需保存一份上千个方法的副本,这显然极不科学。prototype 正是为解决此问题而生。
prototype 属性详解
prototype
无论何时创建新函数,系统都会根据特定规则为该函数创建一个 prototype 属性。默认情况下,prototype 属性包含一个 constructor(构造函数)属性,该属性是指向 prototype 属性所在函数的指针。这一机制略显复杂,下面通过代码和图示说明。
代码如下:
function Person(){
}
根据上图可以看出Person对象会自动获得prototyp属性,而prototype也是一个对象,会自动获得一个constructor属性,该属性正是指向Person对象。
当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(很多浏览器这个指针名字为__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。
代码如下:
function Person(name){
this.name=name;
}
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');

Person的实例person1中包含了name属性,同时自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法,大概就是这个样子的

写段程序测试一下看看prototype内属性、方法是能够共享
代码如下:
function Person(name){
this.name=name;
}
Person.prototype.share=[];
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');
person1.share.push(1);
person2.share.push(2);
console.log(person2.share); //[1,2]
果不其然!实际上当代码读取某个对象的某个属性的时候,都会执行一遍搜索,目标是具有给定名字的属性,搜索首先从对象实例开始,如果在实例中找到该属性则返回,如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object仍然没有则返回错误。同样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数。
代码如下:
function Person(name){
this.name=name;
}
Person.prototype.share=[];
var person=new Person('Byron');
person.share=0;
console.log(person.share); //0而不是prototype中的[]
构造高复用率对象
构造简单对象
当然 prototype 并非专为解决上述问题而定义,但它确实解决了该问题。掌握这些知识后,即可构建更科学、复用率更高的对象。若希望实例对象共享属性或函数,应将其定义到 prototype 中;若希望每个实例拥有独立属性或方法,则定义在 this 中,并通过构造函数传递实例化参数。
代码如下:
function Person(name){
this.name=name;
}
Person.prototype.share=[];
Person.prototype.printName=function(){
alert(this.name);
}
作者:色拉油
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
