原型继承一共有5种方式、

  1. 原型链继承
  2. 借用构造函数
  3. 组合继承(伪经典继承)
  4. 寄生组合继承(经典继承)
  5. 圣杯继承
  6. ES6(最好的继承方式)

原型链继承

原型链继承是最简单的一种继承方式

  1. function Super() { }
  2. function Sub() { }
  3. Sub.prototype = new Super()

借用构造函数继承

  1. function Super() { }
  2. function Sub() {
  3. Super.call(this)
  4. }

组合继承(伪经典继承)

其实就是 原型链继承 + 借用构造函数继承

  1. function Super() { }
  2. function Sub() {
  3. Super.call(this)
  4. }
  5. Sub.prototype = new Super()

寄生组合继承(经典继承)

  1. function Super() { }
  2. function Sub() {
  3. Super.call(this)
  4. }
  5. Sub.prototype = Object.create(Super.prototype)

圣杯继承

使用比较正规时间长的一种继承方式,当年雅虎一直在使用的继承方式

  1. function inherit(Origin, Target) {
  2. function Buffer() { }
  3. Buffer.prototype = Origin.prototype
  4. Target.prototype = new Buffer()
  5. Target.prototype.constructor = Target
  6. Target.prototype.super_class = Origin
  7. }

ES6

JavaScript官方出的一种继承方式,没有引用值共享的问题。是最好的继承方式,但需要使用到ES6的语法糖 class

  1. class Super {
  2. constructor() { }
  3. }
  4. class Sub extends Super {
  5. constructor() {
  6. super()
  7. }
  8. }

ES6与其他几种方式的区别

除了ES6以外,所有的继承方式都有一个共同的缺点。
被继承的构造函数都会有跟他所继承的函数产生共享引用值问题
而这些问题在ES6中被完美的解决了

  1. // 这里我们用被使用最多的圣杯继承方式来做比较
  2. function inherit(Origin, Target) {
  3. function Buffer() { }
  4. Buffer.prototype = Origin.prototype
  5. Target.prototype = new Buffer()
  6. Target.prototype.constructor = Target
  7. Target.prototype.super_class = Origin
  8. }
  9. function Father() { }
  10. Father.prototype.arr = [1, 2, 3, 4, 5]
  11. function Son() { }
  12. inherit(Father, Son)
  13. // 生成两个实例对象
  14. var father = new Father()
  15. var son = new Son()
  16. // 这里我们输出一下两个 arr
  17. // 可以看到他们都是 [1, 2, 3, 4, 5] Son 成功的继承下来了
  18. console.log('更改前 father', father.arr); // [1, 2, 3, 4, 5]
  19. console.log('更改前 son', son.arr); // [1, 2, 3, 4, 5]
  20. // 更改一下 son 上的 arr 看看会不会影响到 father 上
  21. son.arr.push('a')
  22. // 这里可以看到输出结果两个都为 [1, 2, 3, 4, 5, 'a']
  23. // 所以圣杯模式中依然会有引用值共享的问题
  24. console.log('更改后 father', father.arr); // [1, 2, 3, 4, 5, 'a']
  25. console.log('更改后 son', son.arr); // [1, 2, 3, 4, 5, 'a']

输出结果
image.png
在让我们来看看ES6中class的继承

  1. // 创建一个构造函数(class类) Father
  2. class Father {
  3. arr = [1, 2, 3, 4, 5]
  4. }
  5. // 创建一个构造函数(class类) Son 继承于 Father
  6. class Son extends Father {
  7. }
  8. // 同样的生成两个实例对象
  9. var father = new Father()
  10. var son = new Son()
  11. // 同样的这里我们输出一下两个 arr
  12. // 可以看到他们都是 [1, 2, 3, 4, 5] Son 成功的继承下来了
  13. console.log('更改前 father', father.arr) // [1, 2, 3, 4, 5]
  14. console.log('更改前 son', son.arr) // [1, 2, 3, 4, 5]
  15. // 这里我们只在 son.arr 的数组上 push 看看还会不会印象到 father
  16. son.arr.push('a')
  17. // 根据输出结果我们可以很直观的看到 son 中的 arr引用值 并没有影响到 father
  18. console.log('更改后 father', father.arr) // [1, 2, 3, 4, 5]
  19. console.log('更改后 son', son.arr) // [1, 2, 3, 4, 5, 'a']

输出结果
image.png

其实不管多深层的引用值都可以被完美的拷贝下,有兴趣的可以自己去试一试

总结

可以看到上面的几种方式,其实最好的还是ES6。
ES6提供的语法糖不仅可以使我们更好的去创建构造函数,使得代码更清晰可读性更高以外,同时也规避了许多不容易被人注意到的问题