在本教程中,您将学习如何在 ES6 使用 extendssuper 实现 JavaScript 继承。

使用 extends 和 super 实现 JavaScript 继承

在 ES6 之前,实现适当的继承需要多个步骤。最常用的策略之一是原型继承

下面说明了如何使用原型继承技术,使 Bird 继承 Animal 属性:

function Animal(legs) {
    this.legs = legs;
}

Animal.prototype.walk = function() {
    console.log('walking on ' + this.legs + ' legs');
}

function Bird(legs) {
    Animal.call(this, legs);
}

Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Animal;


Bird.prototype.fly = function() {
    console.log('flying');
}

var pigeon = new Bird(2);
pigeon.walk(); // walking on 2 legs
pigeon.fly();  // flying

ES6 通过使用 extendssuper 关键词简化了这些步骤。

下面的例子定义 AnimalBird 类,并通过 extendssuper 关键词建立继承关系。

class Animal {
    constructor(legs) {
        this.legs = legs;
    }
    walk() {
        console.log('walking on ' + this.legs + ' legs');
    }
}

class Bird extends Animal {
    constructor(legs) {
        super(legs);
    }
    fly() {
        console.log('flying');
    }
}


let bird = new Bird(2);

bird.walk();
bird.fly();

代码是如何运行的。

首先,使用 extends 关键词使 Bird 类继承 Animal 类:

class Bird extends Animal { // ...}

Animal 类称为基类父类,而 Bird 类称为派生类子类。通过 extends 关键词,Bird 类继承 Animal 类的所有方法和属性。

其次,在 Bird 的构造函数调用 super()并使用 legs 参数调用 Animal 的构造函数。

如果子类在有构造函数,JavaScript 在情况下要求调用 super()。正如您在 Bird 类中看到的,这 super(legs) 等同于 ES5 的以下语句:

Animal.call(this, legs);

如果 Bird 类没有构造函数,则无需执行任何其他操作:

class Bird extends Animal {
    fly() {
        console.log('flying');
    }
}

它等效于以下类声明:

class Bird extends Animal {
    constructor(...args) {
        super(...args);
    }
    fly() {
        console.log('flying');
    }
}

但是,子类有一个构造函数,它需要调用 super()。例如,以下代码会导致错误:

class Bird extends Animal {
    constructor(legs) {
    }
    fly() {
        console.log('flying');
    }
}

错误:

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

因为 super() 初始化 this 对象,所以需要在访问 this 对象之前调用 super()。在调用 super() 之前尝试访问 this 也会导致错误。

例如,如果你想初始化 Bird 类的属性 color,你可以这样做:

class Bird extends Animal {
	constructor(legs, color) {
		super(legs);
		this.color = color;
	}
	fly() {
		console.log("flying");
	}
	getColor() {
		return this.color;
	}
}

let pegion = new Bird(2, "white");
console.log(pegion.getColor());

影子方法

ES6 允许子类和父类有同名的方法。在这种情况下,当你调用子类对象的方法时,子类中的方法会遮蔽父类的方法。

下面的 Dog 类扩展 Animal 类并重新定义 walk() 方法:

class Dog extends Animal {
    constructor() {
        super(4);
    }
    walk() {
        console.log(`go walking`);
    }
}

let bingo = new Dog();
bingo.walk(); // go walking

要在子类中调用父类的方法,可以这样调用 super.method(arguments)

class Dog extends Animal {
    constructor() {
        super(4);
    }
    walk() {
        super.walk();
        console.log(`go walking`);
    }
}

let bingo = new Dog();
bingo.walk();
// walking on 4 legs
// go walking

继承静态成员

除了属性和方法外,子类还继承父类的所有静态属性和方法。例如:

class Animal {
    constructor(legs) {
        this.legs = legs;
    }
    walk() {
        console.log('walking on ' + this.legs + ' legs');
    }
    static helloWorld() {
        console.log('Hello World');
    }
}

class Bird extends Animal {
    fly() {
        console.log('flying');
    }
}

在此示例中,Animal 类有静态方法 helloWorld(),并且此方法 Bird.helloWorld() 与  Animal.helloWorld() 方法的行为相同:

Bird.helloWorld(); // Hello World

从内置类型继承

JavaScript 允许您通过继承扩展内置类型,例如 Array、 String 、MapSet

下面 Queue 类扩展引用类型 Array。语法比 Queue 使用构造函数/原型模式实现的要简洁得多。

class Queue extends Array {
    enqueue(e) {
        super.push(e);
    }
    dequeue() {
        return super.shift();
    }
    peek() {
        return !this.empty() ? this[0] : undefined;
    }
    empty() {
        return this.length === 0;
    }
}

var customers = new Queue();
customers.enqueue('A');
customers.enqueue('B');
customers.enqueue('C');

while (!customers.empty()) {
    console.log(customers.dequeue());
}

结论

  • 在 ES6 使用 extends 关键词实现继承。要扩展的类称为基类或父类。扩展基类或父类的类称为派生类或子类。
  • super(arguments) 在子类的构造函数中调用调用父类的构造函数。
  • 使用 super 关键词在子类的方法中调用父类的方法。