深入理解JavaScript原型链与继承机制

作者:拾光

时间:2026-10-21

浏览:194

本文深入解析JavaScript中对象与prototype指针的关系,阐述原型链继承的核心原理。通过对比直接赋值与new中间函数的实现方式,详细剖析constructor属性的修正、实例属性覆盖机制以及Object.getPrototypeOf的正确用法,帮助开发者掌握正确的继承模式并避免常见陷阱。

文章目录

JavaScript对象与原型基础

在JavaScript中,对象通常由一个指向prototype的指针以及自身的属性列表构成。JavaScript在创建对象时遵循写时复制(Copy-on-Write)的理念。

只有构造函数具备prototype属性,而原型链继承的本质是创建一个新的指针,使其指向该构造函数的prototype属性。

prototype属性之所以特殊,是由JavaScript读取属性时的遍历机制决定的。从本质上看,它仅仅是一个普通的指针。

内置构造函数与原型链

常见的内置构造函数包括:

1. Object 2. Function 3. Array 4. Date 5. String

接下来通过具体示例进行说明。


//每个function都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数
//注意Person.constructor 不等于 Person.prototype.constructor. Function实例自带constructor属性
functionPerson(name){
this.name = name;
};
Person.prototype.getName =function(){
returnthis.name;
};
var p =newPerson("ZhangSan");
console.log(Person.prototype.constructor===Person);// true
console.log(p.constructor===Person);// true ,这是因为p本身不包含constructor属性,所以这里其实调用的是Person.prototype.constructor

原型继承的目标与矛盾

我们的目标主要包含两点:

1. 表明Person类继承自Animal类

2. 表明p2是Person类的实例

通过修改prototype属性的指向,使Person能够获取Animal中prototype属性所包含的方法,从而实现Person对Animal的继承(即人是野兽)。


functionPerson(name){
this.name = name;
};
Person.prototype.getName =function(){
returnthis.name;
};
var p1 =newPerson("ZhangSan");
console.log(p.constructor===Person);// true
console.log(Person.prototype.constructor===Person);// true
functionAnimal(){}
Person.prototype =newAnimal();//之所以不采用Person.prototype = Animal.prototype,是因为new 还有其他功能,最后总结。
var p2 =newPerson("ZhangSan");
//(p2 -> Person.prototype -> Animal.prototype, 所以p2.constructor其实就是Animal.prototype.constructor)
console.log(p2.constructor===Person);// 输出为false ,但我们的本意是要这里为true的,表明p2是Person的实例。此时目的1达到了,目的2没达到。

如果采用如下修正方式:


Person.prototype = new Animal();
Person.prototype.constructor = Person;

此时p2.constructor确实指向了Person,表明p2是Person类的实例,但引发了新的问题:目的2虽然达成,但目的1未能实现。

目的1和目的2在此时相互矛盾,因为此时的prototype同时表达了两个冲突的含义:

1. 表示父类是谁

2. 作为自身实例的原型进行复制

因此,我们不能直接使用prototype属性来判断父类,而应使用getPrototypeOf()方法来确认父类身份。


Person.prototype =newAnimal();
Person.prototype.constructor=Person;
var p2 =newPerson("ZhangSan");
p2.constructor//显示 function Person() {}
Object.getPrototypeOf(Person.prototype).constructor//显示 function Animal() {}

这样将两个概念分离,通过hasOwnProperty()方法,可以清晰地区分访问的是实例属性还是原型属性。

new运算符与this指向

new运算符执行了哪些操作?

当执行代码var p = new Person()时,new运算符执行以下操作:

创建一个空白对象

创建一个指向Person.prototype的指针

将该对象通过this关键字传递给构造函数并执行构造函数。

具体而言,在以下代码中:

Person.prototype.getName =function(){}

如果我们通过:


var person =newPerson();
其实类似于
var person =newObject();
person.getName =Person.prototype.getName;

因此,通过person.getName()调用方法时,this指向的是新创建的对象,而非prototype对象。

原型扩展与Function.prototype

这在为现有函数添加新功能时会用到,我们可以这样扩展现有方法:


//function myFunc 的写法基本上等于 var myFunc = new Function();
function myFunc (){
}
myFunc =function(func){
  //可以在这里做点其他事情
returnfunction(){
     //可以在这里做点其他事情
return func.apply(this,arguments);
}
}(myFunc)

也可以在Function.prototype方法中直接通过this访问上述代码中myFunc所指向的对象。


function myFunc (){
}
if(!Function.prototype.extend){
Function.prototype.extend =function(){
var func =this;
returnfunction(){
func.apply(this,arguments);
}
}
};
var myFunc = myFunc.extend();

正确的继承实现方案

总结

如果采用Person.prototype = Animal.prototype来表示Person继承自Animal,instanceof方法同样会显示p也是Animal的实例,返回true。

不采用此方法的原因主要有两点:

1. new创建了一个新对象,从而避免了在设置Person.prototype.constructor = Person时导致Animal.prototype.constructor的值变为Person,而是动态给这个新创建的对象一个constructor实例属性。这样实例上的属性constructor就覆盖了Animal.prototype.constructor,使得Person.prototype.constructor和Animal.prototype.constructor分开。

2. Animal自身的this对象的属性无法传递给Person。

但如果像下面这样直接调用构造函数,可能会失败或产生其他影响。

Person.prototype =newAnimal();

为了避免这种情况,我们引入了一个中间函数。因此正确的做法应该是:


Person.prototype =(funtion(){
   function F(){};
   F.prototype =Animal.prototype
   returnnew F();
})()

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持本站!

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章