类是 ES6 新增的一个语法糖,用于实现面向对象编程。在类的继承方面,ES6 引入了子类的概念,让我们能够更加方便地实现类的继承。在本章节中,将结合代码示例,深入探讨 ES6 子类的用法和实现原理。
1、子类的定义和基本用法
子类是基于父类的一种扩展,可以继承父类的属性和方法,并且可以添加自己的属性和方法。在 ES6 中,我们可以使用 extends 关键字来定义子类,语法如下:
class ChildClass extends ParentClass {
// 子类的其他代码
}
其中,ChildClass 表示子类的名称,ParentClass 表示父类的名称。子类中可以定义自己的属性和方法,这些属性和方法会覆盖父类中同名的属性和方法。同时,子类也可以使用 super 关键字来调用父类的构造函数和方法。下面是一个简单的示例代码:
class Animal {
constructor(name) {
this.name = name;
}
sayName() {
console.log(`My name is ${this.name}`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof!');
}
}
const myDog = new Dog('Fido');
myDog.sayName(); // 输出 "My name is Fido"
myDog.bark(); // 输出 "Woof!"
在这个示例中,Animal 是父类,Dog 是子类。Dog 继承了 Animal 的构造函数和 sayName 方法,并且新增了自己的 bark 方法。通过 super 关键字,Dog 在构造函数中调用了父类的构造函数,同时在 sayName 方法中调用了父类的 sayName 方法。
2、子类的实现原理
在 JavaScript 中,类的继承是通过原型链实现的。ES6 的子类继承也是基于原型链的,它的实现原理可以归纳为以下几个步骤:
在上面的示例代码中,Dog 继承了 Animal 的构造函数和方法,这些属性和方法都被复制到了 Dog 的实例对象中。同时,Dog 的原型被设置为 Animal 的实例,从而让 Dog 可以继承 Animal 的方法。在 Dog 中定义的 bark 方法则是子类自己新增的方法。
注意:当子类的原型设置为父类的实例时,父类的构造函数会被调用一次。这是因为在设置子类的原型时,JavaScript 引擎会自动调用父类的构造函数来初始化子类的实例。在示例代码中,Dog 的实例对象被创建时,父类的构造函数被自动调用一次,因此我们在 Dog 的构造函数中必须调用 super 关键字来初始化父类的属性。
3、子类的继承方式
在 ES6 中,子类有两种继承方式:类继承和构造函数继承。下面分别介绍这两种继承方式的用法和特点。
3.1 类继承
类继承是 ES6 中子类的默认继承方式,它通过 extends 关键字来实现。在类继承中,子类继承了父类的所有属性和方法,并且可以添加自己的属性和方法。在子类中,通过 super 关键字来调用父类的构造函数和方法。
类继承的一个特点是,子类的实例对象是父类的实例对象。也就是说,子类继承了父类的构造函数和属性,因此子类的实例对象和父类的实例对象是相同的类型。下面是一个示例代码:
class Parent {
constructor(name) {
this.name = name;
}
sayName() {
console.log(`My name is ${this.name}`);
}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
sayAge() {
console.log(`I am ${this.age} years old`);
}
}
const parent = new Parent('Tom');
const child = new Child('Jerry', 6);
console.log(parent instanceof Parent); // 输出 true
console.log(child instanceof Child); // 输出 true
console.log(child instanceof Parent); // 输出 true
parent.sayName(); // 输出 "My name is Tom"
child.sayName(); // 输出 "My name is Jerry"
child.sayAge(); // 输出 "I am 6 years old"
在这个示例中,Parent 是父类,Child 是子类。Child 继承了 Parent 的构造函数和 sayName 方法,并且新增了自己的 sayAge 方法。通过 super 关键字,Child 在构造函数中调用了父类的构造函数,同时在 sayName 方法中调用了父类的 sayName 方法。
3.2 构造函数继承
构造函数继承是 ES6 中另一种子类继承的方式,它通过在子类中调用父类的构造函数来实现继承。在构造函数继承中,子类不会继承父类的原型对象,因此子类的实例对象不是父类的实例对象。下面是一个示例代码:
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log(`My name is ${this.name}`);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype.sayAge = function() {
console.log(`I am ${this.age} years old`);
};
const parent = new Parent('Tom');
const child = new Child('Jerry', 6);
console.log(parent instanceof Parent); // 输出 true
console.log(child instanceof Child); // 输出 true
console.log(child instanceof Parent); // 输出 false
parent.sayName(); // 输出 "My name is Tom"
child.sayName(); // 报错,sayName 方法不在 Child 的原型对象中
child.sayAge(); // 输出 "I am 6 years old"
在这个示例中,Parent 和 Child 的定义方式与前面示例相同。在 Child 的构造函数中,通过 Parent.call(this, name) 调用了父类的构造函数,以实现继承父类的属性。Child 的原型对象中新增了自己的 sayAge 方法,但没有继承父类的原型对象中的 sayName 方法,因此在 child.sayName() 中会报错。
构造函数继承的一个特点是,它只继承了父类的属性,而没有继承父类的原型对象。因此,构造函数继承的适用场景相对较少,一般只在需要继承父类的属性时使用。
小结
ES6 的子类继承机制使得 JavaScript 中的面向对象编程更加方便和灵活。通过继承父类的属性和方法,子类可以更加高效地实现自己的逻辑,而不必重复编写已经实现过的代码。同时,子类也可以通过新增自己的属性和方法来满足自己的需求。