JavaScript实现继承的三种方式:类式、原型式与掺元类

作者:异步漫步

时间:2026-10-28

浏览:152

本文深入解析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方法运行结果如下:

JavaScript继承机制概述

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

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章