JavaScript prototype属性详解
本文详细解析JavaScript中prototype属性的工作原理。通过对比私有、静态及实例变量,阐述直接定义在构造函数中的方法会导致内存浪费。重点讲解prototype如何实现属性和方法的共享,以及__proto__在原型链搜索中的作用,帮助开发者构建高效、可复用的对象模型。
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属性。默认情况下,该属性会获得一个constructor(构造函数)属性,该属性是一个指向prototype属性所在函数的指针。这一机制可能略显抽象,下面通过代码和图示进行说明。
function Person(){
}

从图示可以看出,Person对象会自动获得prototype属性,而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并非专门为解决前述问题而定义,但它确实有效解决了该问题。掌握这些知识后,可以构建更科学、复用率更高的对象模型:若希望实例对象的属性或函数被共享,则将其定义到prototype中;若希望每个实例单独拥有属性或方法,则定义在this中,并通过构造函数传递实例化参数。
function Person(name){
this.name=name;
}
Person.prototype.share=[];
Person.prototype.printName=function(){
alert(this.name);
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
JavaScript异步发送HTTP请求时怎么优雅地处理错误
2026-11-05 17:10:44
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
