JavaScript实现继承的几种核心方式详解

作者:函数茶馆

时间:2026-10-29

浏览:653

本文深入解析JavaScript中实现继承的三种主要方式:原型链、借用构造函数及组合继承。详细阐述原型链的工作原理及其引用共享缺陷,介绍借用构造函数解决参数传递与属性隔离的方法,并重点讲解结合两者优势的组合继承模式,这是JavaScript中最常用的继承方案。

文章目录

JavaScript继承机制概述

ECMAScript仅支持实现继承,其核心机制主要依赖于原型链来完成。

原型链实现原理

原型链

原型链的核心逻辑在于利用原型对象,使一个引用类型能够继承另一个引用类型的属性与方法。每个构造函数均拥有一个原型对象,该原型对象内含一个指向构造函数的指针;同时,实例对象包含一个指向原型对象的指针。若将原型对象A赋值为另一类型B的实例,则原型对象A将拥有指向B原型对象的指针,而B的原型对象中保存着指向其构造函数的指针。若B的原型对象又是另一类型的实例,此关系层层递进,便构成了实例与原型的链条。

JavaScript继承机制概述 

实例、构造函数与原型之间的关系示意图如下所示:

JavaScript继承机制概述 

此时,person.constructor指向Parent,原因是Child.prototype指向了Parent的原型,而Parent原型对象的constructor属性指向Parent。

当以读取模式访问实例属性时,系统首先在实例中搜索该属性;若未找到,则继续搜索实例的原型。在通过原型链实现的继承中,搜索过程会沿原型链向上进行,直至到达原型链的末端。

例如,调用person.getParentValue()方法时,执行顺序为:1)搜索实例;2)搜索Child.prototype;3)搜索Parent.prototype;找到getParentValue()方法后停止搜索。

1、默认的原型

前述示例中的原型链缺少一环,所有引用类型默认继承自Object,该继承同样通过原型链实现。因此,默认原型包含一个内部指针,指向Object.prototype,这也是所有自定义类型能继承toString()、valueOf()等默认方法的根本原因。换言之,Object.prototype即为原型链的末端。

JavaScript继承机制概述 

2、确定原型和实例的关系

可通过两种方式确认原型与实例的关系:一是使用instanceOf操作符,二是使用isPrototypeOf()方法。实例instanceOf原型链中出现的任何构造函数,均返回true。


console.log(person instanceOf Child);//true 

console.log(person instanceOf Parent);//true 
console.log(person instanceOf Object);//true 
isPrototype(),只要是原型链中出现过的原型,都可以说是该原型链所派生出来的实例的原型,因此也返回true. 
console.log(Object.prototype.isPrototypeOf(instance));//true 
console.log(Parent.prototype.isPrototypeOf(instance));//true 
console.log(Child.prototype.isPrototypeOf(instance));//true 

3、谨慎地定义方法

子类型有时需覆盖超类型中的某个方法,或添加超类型中不存在的方法。需注意:给原型添加方法的代码必须置于替换原型的语句之后。

当通过Child的实例调用getParentValue()时,调用的是重新定义的方法;但通过Parent的实例调用getParentValue()时,调用的仍是原方法。

需特别注意:必须在替换Parent的原型之后,再定义这两个方法。

另一关键点:通过原型链实现继承时,禁止使用对象字面量创建原型方法,因为这会重写原型链。

JavaScript继承机制概述 

上述代码在将Parent的实例赋值给Child的原型对象后,紧接着又将原型替换为字面量。替换后,Child原型实际包含的是Object的实例,而非Parent的实例,导致预期的原型链被切断,Parent与Child之间不再存在关联。

4、原型链的问题

原型链功能强大,可实现继承,但也存在主要问题:包含引用类型值的原型属性会被所有实例共享。因此,我们通常在构造函数中定义实例属性。但在通过原型实现继承时,原型对象变为另一类型的实例,导致原本定义在构造函数中的实例属性变成了原型属性。

举例说明如下:

JavaScript继承机制概述 

在Parent构造函数中定义了一个friends属性,其值为数组(引用类型值)。这样,Parent的每个实例各自包含自己的friends属性。当Child通过原型链继承Parent后,Child.prototype也拥有了friends属性——仿佛该属性定义在Child.prototype中。此时,Child的所有实例共享此friends属性,对kid1.friends的修改也会在kid2.friends中体现,这显然不符合预期。

原型链的另一个问题是:在创建子类型实例时,无法在不影响所有对象实例的情况下,向超类型的构造函数传递参数。因此,通常很少单独使用原型链。

借用构造函数技术

借用构造函数

为解决原型中包含引用类型值带来的问题,引入了借用构造函数的技术。其基本思想是:在子类型构造函数的内部调用超类型构造函数。

JavaScript继承机制概述 

Parent.call(this)在新创建的Child实例的环境中执行了Parent构造函数。在新创建的Child实例环境下调用Parent构造函数,即在新的Child对象(如kid1和kid2)上执行Parent()函数中定义的对象初始化代码。这样,每个Child实例都拥有自己friends属性的副本。

借用构造函数的方式允许在子类型的构造函数中向超类型构造函数传递参数。

JavaScript继承机制概述 

为确保子类型的属性不被父类构造函数重写,可在调用父类构造函数后,再添加子类型的属性。\n\n构造函数的问题:

构造函数模式的问题在于,方法均在构造函数中定义,无法实现函数复用,因此借用构造函数的模式也很少单独使用。

组合继承模式

组合继承

组合继承将原型链和借用构造函数的技术结合,发挥二者之长。即:使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。

JavaScript继承机制概述 

Person构造函数定义了两个属性:name和friends。Person的原型定义了一个方法sayName()。Child构造函数在调用Parent构造函数时传入name参数,随后定义自己的属性age。接着将Person的实例赋值给Child的原型,并在该原型上定义方法sayAge()。这样,两个不同的Child实例既分别拥有自己的属性(包括引用类型属性),又可使用相同的方法。\n\n组合继承避免了原型链和构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。此外,instanceOf和isPropertyOf()也能识别基于组合继承创建的对象。

最后,关于JS对象和继承还有几种模式未涉及,或本人尚未深入研究。但首要目标是熟练应用组合模式,并知其然更知其所以然。

关于JavaScript实现继承的几种方式(推荐),小编就给大家介绍到这里,希望对大家有所帮助!

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章