Javascript继承知多少?

开发 前端
我们知道面向对象的编程语言中都会有继承,而且在各种语言中充当着至关重要的角色,但是继承是什么?又有多少种继承的方式?常见的又有哪些呢?对于js继承有更深层次的理解,能够在开发中应对自如。

[[438533]]

1写在前面

我们知道面向对象的编程语言中都会有继承,而且在各种语言中充当着至关重要的角色,但是继承是什么?又有多少种继承的方式?常见的又有哪些呢?对于js继承有更深层次的理解,能够在开发中应对自如。

所谓继承,继承是面向对象的,使用这种方式能够更好的对代码的复用,能够缩短开发周期、提升开发效率。

那么我们带着两个问题阅读文章,在文章解决这些疑惑:

  • JS继承到底有多少种继承方式?
  • ES6中的extends关键字是使用哪种继承方式实现的?

2继承的概念

我们知道一个人继承祖业,可以将父辈所有的物质基础继承过来,但是自己作为主体又有自己的其它能力和特性。同样的汽车作为一个大类,生产轿车、跑车、面包车等,都具有汽车四个轮子加发动机的特性,但各自具有自己独特的特性,比如五菱宏光可以在秋名山飙车成为车神。

因此,继承可以使得子类具有父类的各种方法和属性。

常见的继承方式有:

  • 原型链继承
  • 构造函数继承
  • 组合继承
  • 原型链继承
  • 寄生式继承
  • 寄生组合式继承

3继承

3.1 原型链继承

原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例:

  • 每个构造函数都有一个原型对象
  • 原型对象又包含一个指向构造函数的指针
  • 实例则包含一个原型对象的指针
  1. function Person(){ 
  2.     this.name = "person"
  3.     this.abilities = ["吃饭","睡觉","打豆豆"]; 
  4.  
  5. function Student(){ 
  6.     this.study = ["语文","数学","英语"]; 
  7.  
  8. Student.prototype = new Person(); 
  9. console.log(new Student()); 

我们可以看到Student类已经继承了Person类的所有特性:

但是,我们注意到:当使用同一个对象创建实例的时候,内存空间是共享的,当一个发生变化的时候,另外一个也会随之变化。

  1. function Person(){ 
  2.     this.name = "person"
  3.     this.abilities = ["吃饭","睡觉","打豆豆"]; 
  4.  
  5. function Student(){ 
  6.     this.study = ["语文","数学","英语"]; 
  7. Student.prototype = new Person(); 
  8. const stu1 = new Student(); 
  9. const stu2 = new Student(); 
  10. stu1.abilities.push("走路"); 
  11. console.log(stu1.abilities,stu2.abilities); 

我们看到stu1和stu2都是由Student对象进行创建的两个实例,当改变stu1的值,stu2的值也随之改变了。图片

3.2 构造函数继承(借助call)

构造函数继承可以很好的解决原型链继承的共享内存的弊端,但是父类原型对象中存在父类之前自己定义的方法,那么子类将无法继承这些方法,此时去使用父类方法就会报错。

构造函数继承只能继承父类实例、属性和方法,不能继承原型属性和方法。

  1. function Person(){ 
  2.     this.name = "person"
  3.  
  4. Person.prototype.getName = function(){ 
  5.     return this.name
  6.  
  7. function Student(){ 
  8.     Person.call(this); 
  9.     this.study = ["语文","数学","英语"]; 
  10.  
  11. const stu = new Student(); 
  12. console.log(stu); 
  13. console.log(stu.getName()) 

我们可以看到,打印的stu实例包含了Student对象以及父类Person所有的属性,但是要使用父类原型的方法就会报错。getName是父类Person的引用方法,不会共享内存。

3.3 组合继承(原型链继承+构造函数继承)

  1. function Person(){ 
  2.     this.name = "person"
  3.     this.abilities = ["吃饭","睡觉","打豆豆"]; 
  4.  
  5. //第一次调用Person() 
  6. Person.prototype.getName = function(){ 
  7.     return this.name
  8.  
  9. function Student(){ 
  10.   //第二次调用Person() 
  11.     Person.call(this); 
  12.     this.study = ["语文","数学","英语"]; 
  13.  
  14. Student.prototype = new Person(); 
  15. //手动挂载构造器,指向自己的构造函数 
  16. Student.prototype.constructor = Student; 
  17. const stu1 = new Student(); 
  18. const stu2 = new Student(); 
  19. stu1.abilities.push("走路"); 
  20. console.log(stu1.abilities,stu2.abilities);//不会互相影响 
  21. console.log(stu1.getName());//正常输出"person" 
  22. console.log(stu2.getName());//正常输出"person" 

运行得到:我们看到在stu1实例的abilities数组中追加元素,并不会影响到stu2实例的值。图片我们发现使用组合式继承时,可以有效解决原型链继承和构造函数继承的缺点,但是,调用两次Person(),这就造成了性能开销,那么我们还有没有优化空间呢?

3.4 原型式继承

我们可以利用es5中的Object.create()方法进行原型式继承,从而实现对组合式继承的优化。Object.create()接收两个参数:

用作新对象原型的对象

为新对象定义额外属性的对象(可选参数)

  1. const person = { 
  2.     name:"person"
  3.     abilities:["吃饭","睡觉","打豆豆"], 
  4.     getName(){ 
  5.         return this.name
  6.     } 
  7.  
  8. const person1 = Object.create(person); 
  9. person1.name = "human"
  10. person1.abilities.push("走路"); 
  11.  
  12. const person2 = Object.create(person); 
  13. person2.abilities.push("跑步"); 
  14.  
  15. console.log(person1.name); 
  16. console.log(person1.name === person1.getName()); 
  17. console.log(person2.name); 
  18. console.log(person1.abilities); 
  19. console.log(person2.abilities); 

我们可以看到使用Object.create()可以实现普通对象继承,不仅可以继承属性,还能继承方法。但是也有缺点:包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

修改person1.name的值,person2.name的值并未发生改变,并不是因为person1和person2有独立的 name 值,而是因为person1.name = 'human',给person1添加了 name 值,并非修改了原型上的 name 值。

3.5 寄生式继承

寄生式继承:首先使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法。

寄生式继承相比于原型式继承,还是在父类基础上添加了更多方法。

  1. function clone(original){ 
  2.     const clone = Object.create(original); 
  3.     clone.getAbilities = function(){ 
  4.    return this.abilities; 
  5.     } 
  6.     return clone; 
  7.  
  8. const person = { 
  9.     name:"person"
  10.     abilities:["吃饭","睡觉","打豆豆"], 
  11.     getName(){ 
  12.         return this.name
  13.     } 
  14.  
  15. const person1 = clone(person); 
  16.  
  17. console.log(person1.getName()); 
  18. console.log(person1.getAbilities()); 

运行得到:

3.6 寄生组合式继承

前面分析了五种常见的继承方法,现在综合所有方式的优缺点,可以进行优化改造得到寄生组合式的继承方式,这也是所有继承方式中相对最优的。

  1. function clone(parent,child){ 
  2.     //这里使用Object.create()可以减少组合继承中多进行一次构造函数的过程 
  3.     child.prototype = Object.create(parent.prototype); 
  4.     child.prototype.constructor = child; 
  5.  
  6. function Parent(){ 
  7.     this.name = "parent"
  8.     this.abilities = ["吃饭","睡觉","打豆豆"]; 
  9.  
  10. Parent.prototype.getName = function(){ 
  11.     return this.name
  12.   
  13. function Child(){ 
  14.     Parent.call(this); 
  15.     this.study = ["语文","数学","英语"]; 
  16.  
  17. clone(Parent,Child); 
  18.  
  19. Child.prototype.getStudy =function(){ 
  20.     return this.study; 
  21.  
  22. const child = new Child(); 
  23. console.log(child); 
  24. console.log(child.getName()); 
  25. console.log(child.getStudy()); 

运行得到:

extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,使用例子如下。

  1. class Rectangle { 
  2.     // constructor 
  3.     constructor(height, width) { 
  4.         this.height = height; 
  5.         this.width = width; 
  6.     } 
  7.      
  8.     // Getter 
  9.     get area() { 
  10.         return this.calcArea() 
  11.     } 
  12.      
  13.     // Method 
  14.     calcArea() { 
  15.         return this.height * this.width; 
  16.     } 
  17.  
  18. const rectangle = new Rectangle(10, 20); 
  19. console.log(rectangle.area); 
  20. // 输出 200 
  21.  
  22. ----------------------------华丽的分割线------------------------------------- 
  23. // 继承 
  24. class Square extends Rectangle { 
  25.  
  26.   constructor(length) { 
  27.     super(length, length); 
  28.      
  29.     // 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。 
  30.     this.name = 'Square'
  31.   } 
  32.  
  33.   get area() { 
  34.     return this.height * this.width; 
  35.   } 
  36.  
  37. const square = new Square(10); 
  38. console.log(square.area); 
  39. // 输出 100 

extends继承的核心代码如下,其实现和上述的寄生组合式继承方式一样。

  1. function _inherits(subType, superType) { 
  2.    
  3.     // 创建对象,创建父类原型的一个副本 
  4.     // 增强对象,弥补因重写原型而失去的默认的constructor 属性 
  5.     // 指定对象,将新创建的对象赋值给子类的原型 
  6.     subType.prototype = Object.create(superType && superType.prototype, { 
  7.         constructor: { 
  8.             value: subType, 
  9.             enumerable: false
  10.             writable: true
  11.             configurable: true 
  12.         } 
  13.     }); 
  14.      
  15.     if (superType) { 
  16.         Object.setPrototypeOf  
  17.             ? Object.setPrototypeOf(subType, superType)  
  18.             : subType.__proto__ = superType; 
  19.     } 

4参考文章

《JavaScript常用八种继承方案》

《深入JavaScript继承原理》

5写在最后

 

继承的方法很多,每个实现的方法都比较零散,需要对常见的继承方法进行一个深入系统的分析总结。图片

 

责任编辑:武晓燕 来源: 前端万有引力
相关推荐

2021-12-10 07:47:30

Javascript异步编程

2021-12-03 15:24:45

Javascript数据类型

2021-12-11 18:59:35

JavascriptJSON应用

2021-12-05 08:27:56

Javascript 高阶函数前端

2021-12-07 08:01:33

Javascript 垃圾回收机制前端

2012-02-13 22:50:59

集群高可用

2021-12-06 07:15:48

Javascript作用域闭包

2010-08-16 09:15:57

2013-12-23 14:00:31

Windows 8.2Windows 8.1

2017-07-14 10:51:37

性能优化SQL性能分析

2018-08-31 10:53:25

MySQL存储引擎

2020-09-08 10:56:55

Java多线程存储器

2021-07-22 07:20:24

JS 遍历方法前端

2012-09-10 16:38:40

Windows Ser

2009-05-13 17:31:06

DBAOracleIT

2021-12-09 06:41:56

Python协程多并发

2022-01-06 16:20:04

Java排序算法排序

2018-12-12 15:01:22

开源存储 软件

2010-09-29 09:28:04

DHCP工作原理

2009-03-06 19:19:55

点赞
收藏

51CTO技术栈公众号