JavaScript实现继承的三种方式:类式、原型式与掺元类
本文深入解析JavaScript基于原型的继承机制,详细阐述类式继承、原型式继承及掺元类(Mixin)三种实现方式。通过构造函数与原型链的配合,类式继承模拟传统面向对象;原型式继承通过对象克隆简化流程;掺元类则通过方法扩充实现多重继承,帮助开发者根据场景选择最优方案。
JavaScript继承机制概述
JavaScript虽被归类为面向对象语言,但其继承机制自设计之初便与传统面向对象语言不同,采用的是基于原型的继承模式。尽管核心机制一致,但在实际应用中仍衍生出多种不同的实现路径。
类式继承实现
方法一:类式继承
类式继承旨在模仿传统面向对象语言的继承逻辑,即继承方与被继承方均被视为“类”。具体代码实现如下:
首先,需要定义一个父类(或称超类):
function Person(name){
this.name=name;
}
Person.prototype.getName=function(){
return this.name;
};
在上述父类中,属性name在构造函数内部定义,这确保了继承该类的子类拥有独立的name属性,而非与父类共享。将getName方法挂载至原型对象,旨在让所有子类实例共享同一份方法代码,从而节省内存空间。这意味着,无论创建多少个实例,它们引用的getName方法都指向同一段内存地址,而非各自独立的空间。
接下来定义一个名为extend的继承方法,实现如下:
function extend(subClass,superClass){
var F=function(){};
F.prototype=superClass.prototype;
subClass.prototype=new F();
subClass.prototype.constructor=subClass;
subClass.superClass=superClass.prototype;
if(superClass.prototype.constructor==Object.prototype.constructor){
superClass.prototype.constructor=superClass;
}
}
该方法首先创建一个空函数F,并将其原型指向父类的原型。随后,将子类的原型设置为F的一个实例,以此达成继承父类的目的。由于子类原型被修改,需将修改后原型的constructor属性重新指向子类,以恢复其构造函数身份。此外,给子类挂载superClass属性,使其能调用父类,从而建立父子类之间的关联。
定义子类Author以继承父类Person,代码示例如下:
function Author(name,books){
Author.superClass.constructor.call(this,name);
this.book=books;
}
extend(Author,Person);
Author.prototype.getBooks=function(){
return this.book;
}
在子类构造函数中,通过superClass属性调用父类构造函数,并利用call方法改变this指向,使子类Author继承父类的属性。同时,子类拥有自己的属性book,因此在构造函数中将参数books赋值给属性book。通过extend函数继承父类原型上的属性和方法(注:此处实际仅继承了方法,因为属性是在构造函数中定义的)。此外,Author还拥有自己的方法getBooks,并挂载到对应原型上,实现了在继承基础上的功能扩展。
这种继承方式类似于传统面向对象语言的类式继承。其优点在于符合传统面向对象程序员的思维习惯,易于理解;缺点则是过程较为繁琐,且内存消耗相对较大。因为子类拥有独立的构造函数及原型,且子父类属性完全隔离,即使值相同也无法共享内存空间。
原型式继承实现
方法二:原型式继承
首先定义一个父类,此处不刻意模仿使用构造函数,而是直接采用对象字面量方式定义一个对象,该对象即作为父类:
var Person={
name:'default name',
getName:function(){
return this.name;
}
} ;
与第一种方法类似,该对象包含一个属性name和一个方法getName。
接着定义一个克隆方法,用于实现子类对父类的继承:
function clone(obj){
function F(){}
F.prototype=obj;
return new F();
}
该克隆方法新建一个对象,将其原型指向父类(即参数obj),并返回该对象。
最后,子类通过克隆函数继承父类,示例如下:
var Author=clone(Person);
Author.book=['javascript'];
Author.showBook=function(){
return this.book;
}
此处定义子类,通过clone函数继承父类Person,并拓展了属性book和方法showBook。该子类同样拥有属性name,若其值与父类相同,则无需覆盖;若不同,可通过
Author.name='new name';覆盖该属性,从而获得子类新的name属性值。
相比类式继承,原型式继承更为简洁自然。若子类属性与父类属性值相同且无需修改,两者将共享同一段内存空间(如上述name属性)。其缺点在于对习惯传统面向对象的程序员而言较难理解。若需权衡,此方式通常更为优越。
基于掺元类的多重继承
既然JavaScript采用基于原型的方式实现继承,且每个对象的原型只能指向特定类的实例(无法指向多个实例),那么如何实现多重继承(即让一个类同时具备多个类的方法和属性,且自身不定义这些内容)?
在JavaScript设计模式中,提供了一种掺元类(mixin class)的方式:
首先定义一个掺元类,用于保存常用方法和属性。这些方法和属性可通过扩展方式添加到其他类上,使被添加类具备相应功能。若定义多个掺元类并同时添加给一个类,则该间接实现了“多重继承”。具体实现如下:
掺元类定义:
var Mixin=function(){};
Mixin.prototype={
serialize:function(){
var output=[];
for(key in this){
output.push(key+":"+this[key]);
}
return output.join(',');
}
}
该掺元类包含一个serialize方法,用于遍历自身,输出属性和属性值,并以逗号分隔的字符串形式返回。
定义一个扩充方法,使某个类经过扩充后具备掺元类的属性或方法:
function augment(receivingClass,givingClass){
if(arguments[2]){
for(var i= 2,len=arguments.length;i<len;i++){
receivingClass.prototype[arguments[i]]=givingClass.prototype[arguments[i]];
}
}
else
{
for(methodName in givingClass.prototype){
if(!receivingClass.prototype[methodName]){
receivingClass.prototype[methodName]=givingClass.prototype[methodName];
}
}
}
}
该方法默认接收两个参数:第一个为被扩充的类,第二个为掺元类(用于扩充其他类的类)。若参数多于两个,后续参数均为方法或属性名,表示被扩充类仅继承指定的属性或方法;否则默认继承掺元类的所有属性和方法。该方法的实质是将掺元类原型上的属性和方法扩充(添加)到被扩充类的原型上,使其具备掺元类的属性和方法。
最后,使用扩充方法实现多重继承:
augment(Author,Mixin);
var author= new Author('js',['javascript design patterns']);
alert(author.serialize());
此处定义了一个author类,该类继承自Person父类,同时具备掺元类Mixin的方法和属性。若定义N个掺元类用于扩充该类,它同样能继承其他掺元类的属性和方法,从而实现多重继承。最后,author的serialize方法运行结果如下:

由此可见,该类同时具备Person类、Author类和Mixin类的属性和方法。其中,Person和Mixin的属性和方法均通过“继承”获得。从实际效果来看,这实现了多重继承。
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
