JavaScript prototype属性详解

作者:代码拾荒

时间:2026-10-30

浏览:336

本文详细解析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(){
    
   }


prototype属性基础概念

从图示可以看出,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');


prototype属性基础概念 

Person的实例person1中包含了name属性,同时自动生成了一个__proto__属性,该属性指向Person的prototype。通过这一链接,实例可以访问到prototype内定义的printName方法。

prototype属性基础概念

编写程序测试一下,验证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);
   }


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章