JavaScript学习笔记(十四) 继承
这篇文章将会介绍在 JavaScript 中经常使用的六种继承方式
1.1 原型继承
方法:将子类的原型指向父类的实例
原理:子类在访问属性或调用方法时,往上查找原型链,能够找到父类的属性和方法
function SuperType(name, info) {
// 实例属性(基本类型)
this.name = name || 'Super'
// 实例属性(引用类型)
this.info = info || ['Super']
// 实例方法
this.getName = function() { return this.name }
}
// 原型方法
SuperType.prototype.getInfo = function() { return this.info }
// 原型继承
function ChildType(message) { this.message = message }
ChildType.prototype = new SuperType('Child', ['Child'])
// 在调用子类构造函数时,无法向父类构造函数传递参数
var child = new ChildType('Hello')
// 子类实例可以访问父类的实例方法和原型方法
console.log(child.getName()) // Child
console.log(child.getInfo()) // ["Child"]
// 所有子类实例共享父类的引用属性
var other = new ChildType('Hi')
other.info.push('Temp')
console.log(other.info) // ["Child", "Temp"]
console.log(child.info) // ["Child", "Temp"]
- 缺点:在调用子类构造函数时,无法向父类构造函数传递参数
- 优点:子类实例可以访问父类的实例方法和原型方法
- 缺点:所有子类实例共享父类的引用属性
1.2 构造继承
方法:在子类的构造函数调用父类的构造函数,并将 this
指向子类实例
原理:在构造子类时,调用父类的构造函数初始化子类的属性和方法
function SuperType(name, info) {
// 实例属性(基本类型)
this.name = name || 'Super'
// 实例属性(引用类型)
this.info = info || ['Super']
// 实例方法
this.getName = function() { return this.name }
}
// 原型方法
SuperType.prototype.getInfo = function() { return this.info }
// 构造继承
function ChildType(name, info, message) {
SuperType.call(this, name, info)
this.message = message
}
// 在调用子类构造函数时,可以向父类构造函数传递参数
var child = new ChildType('Child', ['Child'], 'Hello')
// 子类实例可以访问父类的实例方法,但是不能访问父类的原型方法
console.log(child.getName()) // Child
console.log(child.getInfo()) // Uncaught TypeError
// 每个子类实例的属性独立存在
var other = new ChildType('Child', ['Child'], 'Hi')
other.info.push('Temp')
console.log(other.info) // ["Child", "Temp"]
console.log(child.info) // ["Child"]
- 优点:在调用子类构造函数时,可以向父类构造函数传递参数
- 缺点:子类实例可以访问父类的实例方法,但是不能访问父类的原型方法,因此无法做到函数复用
- 优点:每个子类实例的属性独立存在
1.3 组合继承
方法:同时使用原型继承和构造继承,综合两者的优势所在
原理:通过原型继承实现原型属性和原型方法的继承,通过构造继承实现实例属性和实例方法的继承
function SuperType(name, info) {
// 实例属性(基本类型)
this.name = name || 'Super'
// 实例属性(引用类型)
this.info = info || ['Super']
// 实例方法
this.getName = function() { return this.name }
}
// 原型方法
SuperType.prototype.getInfo = function() { return this.info }
// 组合继承
function ChildType(name, info, message) {
SuperType.call(this, name, info)
this.message = message
}
ChildType.prototype = new SuperType()
ChildType.prototype.constructor = ChildType
// 在调用子类构造函数时,可以向父类构造函数传递参数
var child = new ChildType('Child', ['Child'], 'Hello')
// 子类实例可以访问父类的实例方法和原型方法
console.log(child.getName()) // Child
console.log(child.getInfo()) // ["Child"]
// 每个子类实例的属性独立存在
var other = new ChildType('Child', ['Child'], 'Hi')
other.info.push('Temp')
console.log(other.info) // ["Child", "Temp"]
console.log(child.info) // ["Child"]
- 优点:在调用子类构造函数时,可以向父类构造函数传递参数
- 优点:子类实例可以访问父类的实例方法和原型方法
- 优点:每个子类实例的属性独立存在
- 缺点:在实现组合继承时,需要调用两次父类构造函数
2.1 原型式继承
方法:实现一个函数,传入已有对象,在函数内部将新对象的原型指向原有对象,最后返回新对象
原理:返回的新对象继承原有对象,然后根据需求对得到的对象加以修改即可
var superObject = {
name: 'Super',
info: ['Super'],
getName: function() { return this.name }
}
// 原型式继承
function object(o) {
function F() {}
F.prototype = o
return new F()
}
// 创建子类实例必须基于一个已有对象
var childObject = object(superObject)
// 根据需求对得到的对象加以修改
childObject.message = 'Hello'
// 新创建的实例可以访问已有对象的实例属性和实例方法
console.log(childObject.name) // Super
console.log(childObject.getName()) // Super
// 所有新创建的实例共享已有对象的引用属性
var otherObject = object(superObject)
otherObject.info.push('Temp')
console.log(otherObject.info) // ["Child", "Temp"]
console.log(childObject.info) // ["Child", "Temp"]
-
要求:创建子类实例必须基于一个已有对象
-
缺点:所有新创建的实例都会重新定义已有对象的实例方法,因此无法做到函数复用
-
缺点:所有新创建的实例共享已有对象的引用属性
2.2 寄生式继承
方法:创建一个用于封装继承过程的函数,在函数内部以某种方式增强对象,且最后返回对象
原理:基于原型式继承,类似于工厂模式,将增强对象的过程封装到一个函数中
var superObject = {
name: 'Super',
info: ['Super'],
getName: function() { return this.name }
}
// 寄生式继承
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function objectFactory(o) {
var clone = object(o) // 创建对象
clone.message = 'Hello' // 增强对象
return clone // 返回对象
}
// 创建子类实例必须基于一个已有对象
var childObject = objectFactory(superObject)
// 新创建的实例可以访问已有对象的实例属性和实例方法
console.log(childObject.name) // Super
console.log(childObject.getName()) // Super
// 所有新创建的实例共享已有对象的引用属性
var otherObject = object(superObject)
otherObject.info.push('Temp')
console.log(otherObject.info) // ["Child", "Temp"]
console.log(childObject.info) // ["Child", "Temp"]
-
要求:创建子类实例必须基于一个已有对象
-
缺点:所有新创建的实例都会重新定义已有对象的实例方法,因此无法做到函数复用
-
缺点:所有新创建的实例共享已有对象的引用属性
3 寄生式组合继承
方法:借用寄生式继承的思路,结合组合继承的方法,解决组合继承中需要调用两次父类构造函数的问题
原理:通过构造继承实现实例属性和实例方法的继承,通过寄生式继承实现原型属性和原型方法的继承
不用为了指定子类的原型而调用父类的构造函数,而是使用寄生式继承来继承父类的原型,然后指定给子类的原型
function SuperType(name, info) {
// 实例属性(基本类型)
this.name = name || 'Super'
// 实例属性(引用类型)
this.info = info || ['Super']
// 实例方法
this.getName = function() { return this.name }
}
// 原型方法
SuperType.prototype.getInfo = function() { return this.info }
// 寄生式组合继承
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function objectFactory(childType, superType) {
var prototype = object(superType.prototype) // 创建对象
prototype.constructor = childType // 增强对象
childType.prototype = prototype // 将父类原型指定给子类原型
}
function ChildType(name, info, message) {
SuperType.call(this, name, info)
this.message = message
}
objectFactory(ChildType, SuperType)
寄生式组合继承是 JavaScript 中最常用的继承方式,ES6 中新增的 extends 底层也是基于寄生式组合继承的
【 阅读更多 JavaScript 系列文章,请看 JavaScript学习笔记 】