JS 实现继承的几种方式
原型链继承
示例代码
js
function Parent() {
this.name = 'parent'
}
Parent.prototype.say = function() {
console.log(this.name)
}
function Child() {
this.name = 'Child'
}
Child.prototype = new Parent()
const child = new Child()
child.say() // 'Child'特点
- 子类共享父类的原型,父类修改,所有子类都受影响
- 无法向父类的构造函数传递参数
构造函数继承继承
示例代码
js
function Parent(name) {
this.name = name
this.colors = ['red', 'blue', 'yellow']
}
Parent.prototype.say = function() {
console.log(this.name)
}
function Child(name) {
Parent.call(this, name)
}
const child = new Child('Child')
console.log(child.name) // 'Child'
console.log(child.colors) // ['red', 'blue', 'yellow']
child.say() // error: child.say is not a function特点
- 可以向父类传递参数
- 仅能继承父类构造函数中的方法和属性,无法继承原型链上方法
- 在有些使用场景下,父类构造函数会被多次调用,影响性能
组合继承(原型链 + 构造函数)
示例代码
js
function Parent(name) {
this.name = name
this.colors = ['red', 'blue', 'yellow']
}
Parent.prototype.say = function() {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name); // 继承构造函数中的属性
this.age = age;
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
const child = new Child('Child', 18);
child.say(); // 'Child'
console.log(child.age) // 18
console.log(child.colors) // ['red', 'blue', 'yellow']特点
- 解决了原型链继承和构造函数继承的短板,是最常用的继承方式
- 父类构造函数被调用了两次,一次在设置原型,另一次是在子类的构造函数中
原型式继承
示例代码
js
function createObject(obj) {
function F() {}
F.prototype = obj
return new F()
}
const parent = {
name: 'Parent',
say: function() {
console.log(this.name);
}
}
const child = createObject(parent)
console.log(child.name) // Parent
child.name = 'Child'
console.log(child.name) // Child
child.say() // Child特点
- 适合从已有对象创建新对象
- 共享原型,修改可能影响其他对象(类型原型链继承,共享原型的问题依赖存在)
寄生式继承
代码示例
js
function createObject(obj) {
function F() {}
F.prototype = obj
return new F()
}
function createChild(parent) {
const child = createObject(parent)
child.say = function() {
console.log(this.name);
}
return child
}
const parent = { name: 'Parent' }
const child = createChild(parent)
child.name = 'Child'
child.say() // Child特点
- 适合为对象添加额外的方法
- 仍然存在原型共享的问题
- 无法复用,每次创建对象都需要重新定义方法
组合寄生式
示例代码
js
function Parent(name) {
this.name = name
this.colors = ['red', 'blue', 'yellow']
}
Parent.prototype.say = function() {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this.name)
this.age = age
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
const child = new Child('Child', 18)
child.say() // Child
console.log(child.age) // 18
console.log(child.colors) // ['red', 'blue', 'yellow']特点
- 效率更高,避免了重复调用父类的构造函数,只在子类的构造函数中调用了一次
- 是现代JavaScript中推荐的继承方式
- 保持了原型链的正确性
ES6 类继承
示例代码
js
class Parent {
constructor(name) {
this.name = name
this.colors = ['red', 'blue', 'yellow']
}
say() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name)
this.age = age
}
}
const child = new Child('Child', 18)
child.say() // Child
console.log(child.age) // 18
console.log(child.colors) // ['red', 'blue', 'yellow']特点
- 语法简单,易于理解,内置对
super的支持,便于调用父类的方法 - 旧版本的浏览器,需要转译处理
几种实现方式的对比
- 原型链继承:简单,共享原型问题
- 构造继承:可传参,但无法继承原型方法
- 组合继承:可传参,可以继承原型方法, 但需要调用两次父类的构造函数
- 寄生式继承:适合对象的拓展,但方法不可复用
- 寄生组合式继承:效率高,推荐使用
- ES6 class 继承:现在,简洁,推荐使用
推荐:优先使用 ES6 class 继承或者寄生组合式继承,理由是:语法清晰、性能较好、易于维护。