前言
继承是面向对象编程中讨论最多的话题,但却是日常业务开发中极易被忽略的一块内容。
本文围绕《Javascript高级程序设计第4版》和面试高频问题进行知识点梳理。
原型链继承
基本思想: 函数Child没有使用默认显式原型,而是替换成函数Parent的实例。使Parent的实例可访问的属性和方法同样存在在函数Child实例的原型链上,从而实现了继承。
function Parent() {
this.address = '火星'
}
Parent.prototype.getAddress = function() {
console.log(this.address)
}
function Child() {
}
Child.prototype = new Parent()
let child1 = new Child('')
console.log(child1.getAddress()) // 火星
Child函数成功继承了函数Parent的属性。
问题:原型上引用类型被所有实例共享。
function Parent() {
this.children = []
this.address = '火星'
}
Parent.prototype.getChildren = function() {
console.log(this.children)
}
function Child() {}
Child.prototype = new Parent()
let child1 = new Child()
let child2 = new Child()
child1.children.push('瑾行')
child2.getChildren() // 瑾行
构造函数继承
基本思想:Child函数执行Parent函数,将当前函数执行上下文this指向child实例。
function Parent(name) {
this.name = name
this.address = '火星'
this.children = []
this.getChildren = function() {
console.log(this.children)
}
}
Parent.prototype.getAddress = function() {
console.log(this.address)
}
function Child(name) {
Parent.call(this, name)
}
let child1 = new Child('瑾行')
let child2 = new Child('七金')
child1.children.push('瑾行')
child2.getChildren() // []
child1.getAddress() // child1.getAddress is not a function
child2.getAddress() // child2.getAddress is not a function
解决了原型链继承引用类型共享的问题,且可传参。
问题:每创建一个实例都会创建一遍方法,且Parent原型链上的方法无法继承。
组合继承
基本思想:方法使用原型链继承,属性使用构造函数继承。
function Parent(name) {
this.name = name
this.address = '火星'
this.children = []
}
Parent.prototype.getChildren = function() {
console.log(this.children)
}
Parent.prototype.getAddress = function() {
console.log(this.address)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
function Child(name) {
Parent.call(this, name)
}
let child1 = new Child('瑾行')
let child2 = new Child('七金')
child1.children.push('瑾行')
child2.getBanCreditCard() // []
child1.getAddress() // 火星
child2.getAddress() // 火星
解决了引用属性共享问题,且避免方法被重复创建的问题。
问题:基本问题解决了,但不难发现,我们会调用两次Parent构造函数,就是创建Child的原型的时候和创建实例的时候,还有待优化。
原型式继承
基本思想:使用Object.create的对parent参数对象浅复制,达到继承parent中的属性和方法。
Object.create的原生实现,将传入的对象作为创建对象的隐式原型。
function objectCreate(o) {
function F(){}
F.prototype = o
return new F()
}
原型式继承思想代码基本实现如下。
let parent = {
name: '爸爸',
address: '火星',
children: []
}
let child1 = Object.create(parent)
let child2 = Object.create(parent)
child1.name = '瑾行'
child2.name = '七金'
child2.children.push('七金')
console.log(child1.name) // 瑾行
console.log(child2.name) // 七金
console.log(child1.children) // ['七金']
问题:引用属性会被实例共享,无法传递参数,具备与原型链继承一样的问题。
寄生式继承
基本思想:在原型式继承上增强浅复制能力,创建一个用于继承方法的函数。
function objectCreateEnhance(o) {
let clone = Object.create(o)
clone.say = function() {
console.log('hello world')
}
return clone
}
问题:没有解决引用属性的问题,函数也同样需要重复创建。
寄生组合式继承
基本思想: 在组合继承的基础上进一步优化,将Parent显示原型浅复制赋值给Child显示原型,解决组合继承创建原型多调用Parent构造函数的问题。
function Parent(name) {
this.name = name
this.address = '火星'
this.children = []
}
Parent.prototype.getChildren = function() {
console.log(this.children)
}
Parent.prototype.getAddress = function() {
console.log(this.address)
}
// 重点思想
function inheritPrototype(child,parent) {
let prototype = Object.create(parent.prototype)
child.prototype = prototype
prototype.constructor = child
}
inheritPrototype(Child, Parent)
function Child(name) {
Parent.call(this, name)
}
let child1 = new Child('瑾行')
let child2 = new Child('七金')
child1.children.push('瑾行')
child2.getBanCreditCard() // []
child1.getAddress() // 火星
child2.getAddress() // 火星
寄生组合式继承是目前最完美的继承方案:只调用一次Parent的构造函数,避免在Parent.prototype上添加多余的属性和方法,原型链保持不变,可以让child1和child2合理共同继承。
extends
ES6新增的extends核心思想和寄生组合继承类似,相当于寄生组合继承的语法糖。
经过babel转义如下:
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
// subClass.prototype = superClass.prototype
// subClass.prototype.constructor = subClass
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
// subClass.__proto__ = superClass
if (superClass) {
Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
}
与 ES5 的区别是子类的隐式原型指向了父类,作用是用于继承父级定义的静态属性(static 关键字定义)。