JavaScript常见继承模式实例小结
本文深入解析JavaScript中的六种常见继承模式,包括原型链继承、借用构造函数、组合继承、原型式继承、寄生式继承及寄生组合式继承。通过具体代码实例对比各模式的优缺点,重点阐述为何寄生组合式继承被视为最理想的实现方式,帮助开发者掌握JavaScript面向对象编程的核心机制。
JavaScript继承概述
本文汇总了JavaScript中几种典型的继承实现方式,旨在为读者提供实用的参考与学习素材。
尽管JavaScript缺乏传统面向对象语言中严格的类定义概念,但它通过独特的机制实现了对象间的继承关系。
在深入阅读前,建议先熟悉原型相关的核心知识。
接下来,我们将逐一介绍各种继承方式的具体实现。
原型链继承
一、原型链继承
原型链继承的核心逻辑在于:将父类的一个实例赋值给子类的原型对象。
具体实现如下:
parentInstance = new Parent();
child.prototype = parentInstance;
这种机制使得子类实例的指向父类实例(即子类的prototype属性),而父类实例的又指向父类构造函数的prototype,从而形成一条完整的原型链。
在JavaScript中,查找实例属性的搜索路径遵循以下规则:
- 首先在当前实例中查找该变量名。
- 若未找到,则沿原型链在当前实例指向的原型中继续查找。
假设存在如下继承层级关系:
grandparent(包含方法grandparent.prototype.sayHello) -> parent -> child
当在child的实例child_ming上调用sayHello方法时,引擎首先在child_ming自身中搜索(仅限该实例独有定义),未找到后,接着搜索其指向的原型,即parent的实例。若在parent实例中仍未找到,则继续搜索parent的原型,即grandparent的实例。最终在grandparent的原型中成功找到该方法。
通过上述过程,成功实现了属性的继承。
然而,如同直接使用prototype创建对象时遇到的问题,若完全依赖原型链继承,会导致那些本应私有但被错误共享的属性难以处理,因为原型中的属性会被所有实例共享。
为了解决这一问题,我们引入借用构造函数的方式。
借用构造函数继承
二、借用构造函数实现继承
借用构造函数继承是指在子类的构造函数内部调用父类的构造函数,从而在子类实例中生成父类的属性。
参考以下代码示例:
function Parent(color){
this.color = color;
this.getColor = function(){ alert(this.color); };
}
function Child(color,size){
Parent.call(this,color);
this.size = size;
}
这是一个基础的借用构造函数继承示例。
通过调用父类构造函数,确保了同一构造函数创建的不同实例,其属性可以拥有独立的值,从而解决了原型链继承中属性共享的问题。
但这种方式也存在缺陷,即方法会被重复定义。每个实例都会拥有自己的方法副本,而通常方法是需要复用的,这种重复定义造成了不必要的内存浪费。
组合继承模式可以有效解决上述问题。
组合继承
三、组合继承(原型链与借用构造函数)
既然原型链擅长属性共享,而借用构造函数擅长属性值的私有化,将两者结合即可形成组合继承。
组合继承的本质是执行一次原型链继承和一次借用构造函数继承。
具体代码实现如下:
function Parent(color){
this.color = color;
}
Parent.prototype.getColor = function(){ alert(this.color); };
function Child(color,size){
Parent.call(this,color); //借用构造函数继承属性
this.size = size;
}
//下面的属性'green'并没有影响,这里要使用原型继承方法
Child.prototype = new Parent('green');
var child_demo = new Child('red',200);
在此代码中,我们需要思考两个关键问题:创建一个child实例究竟生成了几份color属性?代码中既定义了'red'又定义了'green',当调用child_demo.getColor()时,最终alert显示的是哪一个值?
首先分析第一个问题。由于子类构造函数中调用了父类构造函数,创建子类实例时必然生成一份color属性。但需注意,在继承方法时,我们将子类构造函数的原型指向了一个父类实例,创建该父类实例时又生成了一份color属性(即代码中'green'处)。这份属性实际上并无必要,因此总共生成了两份color属性,其中一份是冗余的。
接着分析第二个问题。理解this关键字的含义即可得出结论:
child_demo.getColor() // 'red'
Child.prototype.getColor() //'green'
组合继承结合了前两种模式的优点,但也存在固有缺陷。从生成两份color属性可以看出,继承过程中调用了两次Parent构造函数,这会影响执行效率。尽管如此,瑕不掩瑜,它仍是JavaScript中最常用的继承模式之一。
原型式继承
四、原型式继承
原型式继承基于已有对象创建新对象,是从现有对象基础上的直接继承。
参考以下代码:
var obj = {
color: 'red',
getColor: function(){ alert(this.color); },
};
//getChild(obj)返回的是一个__proto__指向obj的实例
function getChild(obj){
function func(){}
func.prototype = obj;
return new func();
}
var child_demo = getChild(obj);
这种继承方式与原型链继承既有联系又有区别。
相同点在于:两者均通过修改子类构造函数的原型属性来实现继承,且继承的属性都具有实例间共享的特性。
不同点在于:原型链继承中,子类构造函数的原型是父类的一个实例(继承目标可能存在于父类构造函数的原型或直接指向的父类实例中);而原型式继承中,子类构造函数的原型直接是一个已有对象,即父类本身。
寄生式继承
五、寄生式继承
寄生式继承可视为原型式继承的变体,它对原型式继承进行了封装,使得创建子类实例仅依赖于一个函数。
具体代码实现如下:
var obj = {
color: 'red',
getColor: function(){ alert(this.color); },
};
function getChild(obj){
function func(){}
func.prototype = obj;
return new func();
}
/*-------以上是原型式继承的代码----------*/
function betterGetChild(obj,size){
var temp_obj = getChild(obj);
temp_obj.size = size;
temp_obj.getSize = function(){ return this.size; };
return temp_obj;
}
var demo = betterGetChild(obj,200);
寄生组合式继承
六、寄生组合式继承
寄生组合式继承是组合继承与原型式继承的结合应用。由于组合继承中子类构造函数的原型指向父类的一个实例而非父类构造函数的原型,这会导致在设置子类原型时对父类构造函数进行一次额外调用。因此,在寄生组合式继承中,借鉴原型式继承的思想,将父类构造函数的原型视为一个已有对象,让子类构造函数的原型直接指向它。
代码实现如下:
function getChild(obj){
function func(){}
func.prototype = obj;
return new func(); //调用构造函数
}
/*-------以上是原型式继承的代码----------*/
function Parent(color){
this.color = color;
}
Parent.prototype.getColor = function(){
alert(this.color);
};
function Child(color,size){
Parent.call(this,color); //调用构造函数
this.size = size;
}
//Child.prototype直接指向一个__proto__指向Parent.prototype的实例
Child.prototype = getChild(Parent.prototype);
Child.prototype.getSize = function(){ alert(this.size); };
严格来说,这种方式依然调用了两次构造函数,但其中一次是对一个空函数的调用,而非两次都调用父类构造函数。
在组合继承中,子类原型中实际上保存了父类未定义在原型中的属性(因为子类原型是父类实例),只是子类构造函数通过借用父类构造函数在当前实例中生成了覆盖原型属性的同名属性。而在寄生组合式继承中,根本不会产生这些冗余数据。
业界普遍认为,寄生组合式继承是最理想的继承模式。
如需了解更多JavaScript相关内容,可参考本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》。
希望本文的总结对您的JavaScript程序设计有所帮助。
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
