原型继承一共有5种方式、
原型链继承
原型链继承是最简单的一种继承方式
function Super() { }
function Sub() { }
Sub.prototype = new Super()
借用构造函数继承
function Super() { }
function Sub() {
Super.call(this)
}
组合继承(伪经典继承)
其实就是 原型链继承 + 借用构造函数继承
function Super() { }
function Sub() {
Super.call(this)
}
Sub.prototype = new Super()
寄生组合继承(经典继承)
function Super() { }
function Sub() {
Super.call(this)
}
Sub.prototype = Object.create(Super.prototype)
圣杯继承
使用比较正规时间长的一种继承方式,当年雅虎一直在使用的继承方式
function inherit(Origin, Target) {
function Buffer() { }
Buffer.prototype = Origin.prototype
Target.prototype = new Buffer()
Target.prototype.constructor = Target
Target.prototype.super_class = Origin
}
ES6
JavaScript官方出的一种继承方式,没有引用值共享的问题。是最好的继承方式,但需要使用到ES6的语法糖 class
class Super {
constructor() { }
}
class Sub extends Super {
constructor() {
super()
}
}
ES6与其他几种方式的区别
除了ES6以外,所有的继承方式都有一个共同的缺点。
被继承的构造函数都会有跟他所继承的函数产生共享引用值问题
而这些问题在ES6中被完美的解决了
// 这里我们用被使用最多的圣杯继承方式来做比较
function inherit(Origin, Target) {
function Buffer() { }
Buffer.prototype = Origin.prototype
Target.prototype = new Buffer()
Target.prototype.constructor = Target
Target.prototype.super_class = Origin
}
function Father() { }
Father.prototype.arr = [1, 2, 3, 4, 5]
function Son() { }
inherit(Father, Son)
// 生成两个实例对象
var father = new Father()
var son = new Son()
// 这里我们输出一下两个 arr
// 可以看到他们都是 [1, 2, 3, 4, 5] Son 成功的继承下来了
console.log('更改前 father', father.arr); // [1, 2, 3, 4, 5]
console.log('更改前 son', son.arr); // [1, 2, 3, 4, 5]
// 更改一下 son 上的 arr 看看会不会影响到 father 上
son.arr.push('a')
// 这里可以看到输出结果两个都为 [1, 2, 3, 4, 5, 'a']
// 所以圣杯模式中依然会有引用值共享的问题
console.log('更改后 father', father.arr); // [1, 2, 3, 4, 5, 'a']
console.log('更改后 son', son.arr); // [1, 2, 3, 4, 5, 'a']
输出结果
在让我们来看看ES6中class的继承
// 创建一个构造函数(class类) Father
class Father {
arr = [1, 2, 3, 4, 5]
}
// 创建一个构造函数(class类) Son 继承于 Father
class Son extends Father {
}
// 同样的生成两个实例对象
var father = new Father()
var son = new Son()
// 同样的这里我们输出一下两个 arr
// 可以看到他们都是 [1, 2, 3, 4, 5] Son 成功的继承下来了
console.log('更改前 father', father.arr) // [1, 2, 3, 4, 5]
console.log('更改前 son', son.arr) // [1, 2, 3, 4, 5]
// 这里我们只在 son.arr 的数组上 push 看看还会不会印象到 father
son.arr.push('a')
// 根据输出结果我们可以很直观的看到 son 中的 arr引用值 并没有影响到 father
console.log('更改后 father', father.arr) // [1, 2, 3, 4, 5]
console.log('更改后 son', son.arr) // [1, 2, 3, 4, 5, 'a']
输出结果
其实不管多深层的引用值都可以被完美的拷贝下,有兴趣的可以自己去试一试
总结
可以看到上面的几种方式,其实最好的还是ES6。
ES6提供的语法糖不仅可以使我们更好的去创建构造函数,使得代码更清晰可读性更高以外,同时也规避了许多不容易被人注意到的问题