JavaScript中继承有哪几种方式?有什么特点?

83 阅读4分钟

原型链继承

  • 原理:通过将子类的原型对象指向父类的实例,使得子类的实例能够访问父类原型上的属性和方法,从而实现继承关系。
  • 示例代码
function Parent() {
    this.name = 'parent';
    this.sayName = function() {
        console.log(this.name);
    };
}
Parent.prototype.sayHello = function() {
    console.log('Hello from Parent');
};

function Child() {
    this.age = 10;
}
Child.prototype = new Parent();

var child = new Child();
child.sayName(); // 输出:parent
child.sayHello(); // 输出:Hello from Parent
  • 特点:实现简单,子类可以继承父类原型上的属性和方法。但存在一些问题,如多个子类实例共享父类实例的属性,可能会导致数据相互影响;创建子类实例时,无法向父类构造函数传递参数。

构造函数继承

  • 原理:在子类的构造函数中调用父类的构造函数,通过callapply方法改变this指向,使得子类实例拥有父类的属性和方法。
  • 示例代码
function Parent(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    };
}

function Child(name) {
    Parent.call(this, name);
    this.age = 10;
}

var child = new Child('child');
child.sayName(); // 输出:child
  • 特点:可以在子类构造函数中向父类构造函数传递参数,解决了原型链继承中无法传参的问题。但子类无法继承父类原型上的属性和方法,每个子类实例都拥有父类构造函数中的方法,导致方法的重复定义,浪费内存空间。

组合继承

  • 原理:将原型链继承和构造函数继承结合起来,通过原型链继承父类原型上的属性和方法,通过构造函数继承父类实例的属性,从而综合了两者的优点。
  • 示例代码
function Parent(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    };
}
Parent.prototype.sayHello = function() {
    console.log('Hello from Parent');
};

function Child(name) {
    Parent.call(this, name);
    this.age = 10;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child = new Child('child');
child.sayName(); // 输出:child
child.sayHello(); // 输出:Hello from Parent
  • 特点:融合了原型链继承和构造函数继承的优点,既可以继承父类原型上的属性和方法,又可以在子类构造函数中向父类构造函数传递参数,是 JavaScript 中最常用的继承方式之一。但由于调用了两次父类构造函数,一次是在创建子类原型时,一次是在子类构造函数中,会造成一定的性能开销和属性的重复定义。

寄生组合继承

  • 原理:通过寄生式继承的方式来解决组合继承中调用两次父类构造函数的问题,只调用一次父类构造函数,然后通过原型链将子类的原型与父类的原型连接起来,从而实现继承。
  • 示例代码
function Parent(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    };
}
Parent.prototype.sayHello = function() {
    console.log('Hello from Parent');
};

function inheritPrototype(subType, superType) {
    var prototype = Object.create(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

function Child(name) {
    Parent.call(this, name);
    this.age = 10;
}
inheritPrototype(Child, Parent);

var child = new Child('child');
child.sayName(); // 输出:child
child.sayHello(); // 输出:Hello from Parent
  • 特点:是目前较为理想的继承方式,它只调用一次父类构造函数,避免了组合继承中属性的重复定义和性能开销,同时又能继承父类原型上的属性和方法,并且可以在子类构造函数中向父类构造函数传递参数,保持了良好的可维护性和性能。

class类继承

  • 原理:在 JavaScript 中,class 继承基于原型链实现,是一种语法糖形式呈现的面向对象编程中的继承机制。子类通过 extends 关键字声明继承自某个父类,在内部会建立起子类原型与父类原型之间的关联,使得子类的实例能够继承父类原型上的属性和方法。同时,借助 super 关键字来访问和调用父类的构造函数以及原型上的方法,以此保证父类相关的初始化逻辑和已有功能能够被子类复用,并且正确处理实例属性的初始化等操作。

示例代码

// 定义父类
class Parent {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}

// 定义子类,继承自 Parent 类
class Child extends Parent {
    constructor(name, age) {
        // 调用父类的构造函数,传递参数,初始化父类中定义的属性
        super(name);
        this.age = age;
    }
    sayAge() {
        console.log(this.age);
    }
}

// 创建子类的实例
let child = new Child('Tom', 10);
// 调用从父类继承来的方法
child.sayName(); 
// 调用子类自身定义的方法
child.sayAge(); 

特点

  • 语法简洁直观:使用 classextends 和 super 等关键字构建继承关系,代码结构清晰,和传统面向对象语言中的类继承语法风格相近,易于阅读和理解,降低了代码的理解成本,方便开发人员快速把握类之间的继承脉络。
  • 明确继承关系extends 关键字清晰地表明了子类与父类之间的继承关联,能够从代码层面一眼看出继承层次,在大型项目中面对复杂的类体系时,更有助于代码的维护、扩展以及团队成员之间的协作,使代码的整体架构更加清晰明了。