Appearance
JavaScript继承完全指南
前言
继承是面向对象编程的重要概念,JavaScript作为一门基于原型的语言,提供了多种实现继承的方式。本文将详细介绍JavaScript中的各种继承方式及其优缺点。
一、原型链继承
1.1 基本原理
通过将子类的原型指向父类的实例来实现继承。
javascript
function Parent() {
this.name = 'parent';
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child() {}
// 核心:子类原型指向父类实例
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 使用
const child1 = new Child();
const child2 = new Child();
child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
console.log(child2.colors); // ['red', 'blue', 'green', 'black']
1.2 优缺点
优点:
- 继承了父类的属性和方法
- 可以继承父类原型上的属性和方法
缺点:
- 引用类型的属性被所有实例共享
- 创建子类实例时无法向父类构造函数传参
二、构造函数继承
2.1 基本原理
在子类构造函数中调用父类构造函数。
javascript
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
function Child(name) {
// 核心:调用父类构造函数
Parent.call(this, name);
}
// 使用
const child1 = new Child('child1');
const child2 = new Child('child2');
child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
console.log(child2.colors); // ['red', 'blue', 'green']
2.2 优缺点
优点:
- 避免了引用类型的属性被所有实例共享
- 可以向父类构造函数传参
缺点:
- 方法都在构造函数中定义,每次创建实例都会创建一遍方法
- 无法继承父类原型上的方法
三、组合继承
3.1 基本原理
结合原型链继承和构造函数继承。
javascript
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
// 第一次调用父类构造函数
Parent.call(this, name);
this.age = age;
}
// 第二次调用父类构造函数
Child.prototype = new Parent();
Child.prototype.constructor = Child;
Child.prototype.getAge = function() {
return this.age;
};
// 使用
const child1 = new Child('child1', 18);
const child2 = new Child('child2', 20);
child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
console.log(child2.colors); // ['red', 'blue', 'green']
console.log(child1.getName()); // 'child1'
console.log(child2.getAge()); // 20
3.2 优缺点
优点:
- 融合原型链继承和构造函数的优点
- 是JavaScript中最常用的继承模式
缺点:
- 会调用两次父类构造函数
四、寄生组合继承
4.1 基本原理
通过寄生方式,砍掉父类的实例属性,避免了组合继承中多余的父类实例属性。
javascript
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 核心:将父类原型复制给子类
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
inheritPrototype(Child, Parent);
Child.prototype.getAge = function() {
return this.age;
};
// 使用
const child1 = new Child('child1', 18);
const child2 = new Child('child2', 20);
console.log(child1.getName()); // 'child1'
console.log(child2.getAge()); // 20
4.2 优缺点
优点:
- 只调用一次父类构造函数
- 避免了在子类原型上创建多余的属性
- 原型链保持不变
缺点:
- 实现较为复杂
五、ES6 Class继承
5.1 基本语法
使用class关键字实现继承。
javascript
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
getName() {
return this.name;
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类构造函数
this.age = age;
}
getAge() {
return this.age;
}
}
// 使用
const child = new Child('child', 18);
console.log(child.getName()); // 'child'
console.log(child.getAge()); // 18
5.2 特点
- 语法更加清晰、易懂
- 实现了真正的继承
- 必须先调用super()
- 类不存在变量提升
六、实际应用示例
6.1 实现一个简单的组件继承系统
javascript
class Component {
constructor(props = {}) {
this.props = props;
}
setState(state) {
this.state = { ...this.state, ...state };
this.render();
}
render() {
throw new Error('Component must implement render method');
}
}
class Button extends Component {
constructor(props) {
super(props);
this.state = { clicked: false };
}
render() {
return `<button class="${this.state.clicked ? 'active' : ''}">${this.props.text}</button>`;
}
}
const button = new Button({ text: '点击我' });
6.2 实现一个事件发射器
javascript
class EventEmitter {
constructor() {
this._events = {};
}
on(event, callback) {
if (!this._events[event]) {
this._events[event] = [];
}
this._events[event].push(callback);
return this;
}
emit(event, ...args) {
const callbacks = this._events[event];
if (callbacks) {
callbacks.forEach(callback => callback.apply(this, args));
}
return this;
}
}
class Logger extends EventEmitter {
log(message) {
console.log(message);
this.emit('log', message);
}
}
const logger = new Logger();
logger.on('log', message => console.log('Logged:', message));
七、面试常见问题
7.1 原型链继承和class继承的区别?
javascript
// 原型链继承
function Animal() {}
Animal.prototype.eat = function() {};
function Dog() {}
Dog.prototype = new Animal();
// class继承
class Animal {
eat() {}
}
class Dog extends Animal {
constructor() {
super();
}
}
主要区别:
- 语法更加清晰
- class继承必须调用super
- class不存在变量提升
- class内部默认严格模式
7.2 如何实现多继承?
javascript
// 使用混入(Mixin)模式
const MixinA = {
methodA() {}
};
const MixinB = {
methodB() {}
};
class MyClass {
constructor() {
Object.assign(this, MixinA, MixinB);
}
}
八、最佳实践建议
- 优先使用ES6的class继承
- 避免深层次的继承关系
- 优先使用组合而不是继承
- 注意继承带来的耦合问题
- 合理使用super关键字
javascript
// 不好的写法
class A extends B extends C extends D {
// 深层次继承
}
// 好的写法
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {};
}
}
总结
JavaScript提供了多种继承实现方式,每种方式都有其适用场景:
- 原型链继承:最基础的继承方式
- 构造函数继承:解决引用类型共享问题
- 组合继承:最常用的继承方式
- 寄生组合继承:最理想的继承方式
- ES6 class继承:最现代的继承方式
在实际开发中,推荐使用ES6的class继承,它提供了更清晰的语法和更好的功能支持。同时,要注意继承带来的复杂性,合理使用继承来构建代码。