JavaScript原型链重写与实例访问机制解析

作者:纸飞机

时间:2026-10-29

浏览:90

本文深入探讨JavaScript中重写原型对象对实例访问的影响。通过对比代码顺序,揭示重写原型虽切断构造函数与初始原型的联系,但实例能否访问方法取决于对象创建时机与原型链查找机制。文章澄清了对象字面量赋值与属性修改的区别,解释了为何特定顺序下实例无法调用方法,并纠正了关于原型链切断导致访问失败的常见误解。

文章目录

JavaScript原型与构造函数的基础关系

需要明确的是,在JavaScript中,每个对象都具备constructor属性和prototype属性。其中,constructor属性指向创建该对象的构造函数,而prototype属性则指向由该构造函数生成的实例所共享的原型对象。


function Person(){ 
  
 } 
var person = new Person(); 
 
Person.prototype = { 
 constructor : Person, 
 name : 'zxs', 
 age : 24, 
 sayName : function(){alert(this.name)} 
 } 
  
person.sayName(); 

原型重写对实例方法访问的影响

若执行特定代码顺序,调用sayName()会抛出“is not defined”错误。依据《JavaScript高级程序设计》第二版的观点,这归因于原型重写切断了构造函数与原始原型之间的关联。然而,通过调整上述代码的执行顺序,结果会有所不同,具体如下所示。


function Person(){ 
 } 
//var person = new Person(); 
Person.prototype = { 
 constructor : Person, 
 name : 'zxs', 
 age : 24, 
 sayName : function(){alert(this.name)} 
} 
/*===========================================================*/ 
var person = new Person(); 
/*===========================================================*/ 
 person.sayName(); // zxs 
alert(person.constructor) //function Object() { [native code]} or function Person() {} 取决与蓝色的语句是否有效 

请留意上述两段代码中等号左侧的语句差异。按照第二段的顺序执行,程序将输出“zxs”。这一结果证明,第一种情况下的报错并非单纯由构造函数与原型间联系的切断所引起。


Person.prototype = {} 

定义对象本身是一种常规操作,且JavaScript中每个对象的constructor属性默认指向Object构造函数。这解释了为何重写原型对象确实会切断构造函数与初始原型的联系,但这并不意味着切断后person实例就无法访问sayName()函数。

这里存在一种机制假设:函数的prototype属性指向的原型对象,与我们显式新建的原型对象并非完全等同。当调用函数时,系统会创建一个原型对象,首先检查当前环境中是否存在其原型对象;若不存在则创建,若存在则查找其属性和方法,最后根据查找结果返回一个原型对象。该对象中的属性和方法优先使用默认原型(即构造函数中定义的部分)的内容;仅当调用的方法或属性在默认原型中不存在时,才会使用定义在Person.prototype = {}中的属性和方法。

JavaScript作为解释型语言,语句按顺序执行。在第一段代码中,使用new关键字创建新对象时,Person.prototype = {}尚未执行,导致当前执行环境中找不到其中定义的方法和属性,且构造函数中也没有该方法,因此报错。这类似于给变量赋值时程序未执行,导致无法使用。而在第二段代码中,环境中已存在该调用方法,构造函数的原型对象已创建完毕,因此能正常获取结果。

原型属性定义方式与执行顺序分析

观察以下程序示例:


////////////////////////////////////////////////////////////////////////// 
 
function Person(){} 
 
/*===========================================================*/ 
 
 var person = new Person(); 
Person.prototype.name = 'song'; 
 
/*===========================================================*/ 
 
//Person.prototype.sayName = function(){alert(this.name)}; 
Person.prototype = { 
constructor : Person, 
name : 'zxs', 
age : 24, 
sayName : function(){alert(this.name)} 
} 
person.sayName(); // error 
 
////////////////////////////////////////////////////////////////////////// 
 
function Person(){  } 
/*var person = new Person();*/ 
Person.prototype.name = 'song';  
/*Person.prototype.sayName = function(){alert(this.name)};*/ 
Person.prototype = {   
constructor : Person, 
  name : 'zxs', 
  age : 24, 
  sayName : function(){alert(this.name)} 
} 
 
/*===========================================================*/ 
var person = new Person(); 
 
/*===========================================================*/ 
person.sayName(); // zxs 

由此可见,使用Person.prototype.name = ''的方式,无论对象在何处创建均可被访问。若同时存在对象字面量和这种方式定义原型对象,系统将使用后定义的作为最终值。此外,若对原型对象使用对象字面量定义,该定义必须出现在创建对象的语句之前,才能被实例访问到。

实例无法访问原型对象中的属性和方法,原因不仅仅是重写原型对象切断了构造函数与最初原型之间的联系。


function Person(){  
    
  }  
var person = new Person();  
  
Person.prototype = {  
  //constructor : Person,  
  name : 'zxs',  
  age : 24,  
  sayName : function(){alert(this.name)}  
  }  
    
person.sayName();  

实例访问原型对象的深层机制

上述代码在实例化对象时,构造函数的原型为空,除默认属性外没有任何其他属性。重写构造函数的原型确实切断了构造函数与最初原型之间的联系。

在使用new操作符后,构造函数的原型对象中的属性和方法已添加到person对象中。由于上述方法为函数原型添加新属性和方法不具有动态性,因此person无法访问到新添加的属性和方法。

重写原型对象后,情况类似于以下代码:


var o = { 
  name : 'zxs' 
  } 
   
var obj = o; 
o = {} 
console.log(o.name);  

此时输出的值为undefined。因为对象是引用类型,“=”是赋值操作符,且运算顺序从右往左。o = {}意味着o的指向已改变,成为一个空对象。Person.prototype.mothed = function() {}与Person.prototype = {mothed: function(){}}的区别,就如同arr = []和arr.push()一样:前者是修改自身,后者是完全改变自身。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章