整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript面向对象编程-常用的两种继承方法

JavaScript面向对象编程-常用的两种继承方法

家都知道的,JavaScript这门语言在ES6出来之前是没有类(class)这个概念的。

所以JavaScript中的类都是通过原型链来实现的。

既然能实现类,那同样也就可以在JavaScript中实现面向对象的继承了。(喜欢看书的朋友,可以去看一下《JavaScript高级程序设计》第三版6.3小节)。

我们使用继承主要是为了能实现代码的抽象和代码的复用,在应用层实现功能的封装。

在JavaScript中,继承的实现比较复杂,坑也很多,什么属性继承、原型继承、call/aplly继承、原型链继承、对象继承、构造函数继承、组合继承、类继承... 十几种,看着都头晕,对于小白来说,看多了都不知道用哪个好了。

而且每一种都细讲需要花很多时间,所以今天这里大致梳理常用的两种给大家学习,一起来看看吧!

JavaScript 中的继承并不是明确规定的,而是通过模仿实现的。

今天我们来讲两种,一个是原型链实现继承、第二个借用构造函数

先看一下父类

父类

方式一:原型链继承

实现方式:子类的原型指向父类的实例

子类:

代码测试

测试

这种方式会存在的问题:

1.引用类型的对象会被子类的所有实例共享(问题1解决)

2.无法在创建子类的实例时,给父类的构造函数传递参数(问题2解决)

方式二:借用构造函数(又称为伪造对象、经典继承)

实现方式:在子类的构造函数中利用call(或者apply)方法执行父类构造函数(问题2解决),将执行对象设为子类的this,相当于把父类构造函数中的成员拷贝了一份到子类(问题1解决)

子类

子类

测试代码如下:

测试代码

存在的问题:

3.父类原型中定义的属性无法被继承

以上两种方法都是有存在问题的,所以我们现在就出现了第三种方法来实现,你知道第三种方式是怎么实现的吗?欢迎在评论区留言哦!

我们下次继续聊继承的其他方法。

、对象冒充

其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使 Parent 构造函数 成为 Children 的方法,然后调用它。Children 就会收到 Parent 的构造函数中定义的属性和方法。例如,用下面的方式定义 Parent 和 Children:


原理:就是把 Parent 构造函数放到 Children 构造函数里面执行一次。那为什么不直接执行,非要转个弯把 Parent 赋值给 Children 的 method 属性再执行呢? 这跟 this 的指向有关,在函数内 this 是指向 window 的。当将 Parent 赋值给 Children 的 method 时, this 就指向了 Children 类的实例。

二、原型链继承

众所周知,JavaScript 是一门基于原型的语言,在 JavaScript 中 prototype 对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制:


注意:调用 Parent 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。

三、使用 call 或 applay 方法

这个方法是与对象冒充方法最相似的方法,因为它也是通过改变了 this 的指向而实现继承:


apply 方法本人就不举列了,它和 call 方法的区别在于它的第二个参数必须是数组。

四、混合方式

对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。如何选择呢?答案很简单,两者都用。 在 JavaScript 中创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制:


五、使用 Object.create 方法

Object.create 方法会使用指定的原型对象及其属性去创建一个新的对象:


@ 当执行 Children.prototype=Object.create(Parent.prototype) 这个语句后,Children 的 constructor 就被改变为 Parent ,因此需要将 Children.prototype.constructor 重 新指定为 Children 自身。

六、extends 关键字实现继承

这个是 ES6 的语法糖,下面看下es6实现继承的方法:


上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。子类Children的构造函数之中的super(),代表调用父类Parent的构造函数。这是必须的,否则 JavaScript 引擎会报错。

注意,super虽然代表了父类Parent的构造函数,但是返回的是子类Children的实例,即super内部的this指的是Children,因此super()在这里相当于Parent.prototype.constructor.call(this)。

创: 前端二牛

借用构造函数继承解决了原型链数据共享和无法向超类型传递参数的问题,但自身的缺陷是不能使用超类型原型中定义的方法。组合继承是将原型链继承和借用构造函数继承组合到一起,从而发挥二者之长的一种继承模式,也被称作伪经典继承。背后的思路是使用原型链实现原型属性和方法的继承,使用构造函数来实现实例属性的继承。这样既通过在原型上定义方法实现了函数的复用,又能保证每个实例都有它自己的属性。

下面来看一个例子:

  1. function
  2. Human
  3. (
  4. name
  5. )
  6. {
  7. this
  8. .
  9. name
  10. =
  11. name
  12. ;
  13. this
  14. .
  15. colors
  16. =
  17. [
  18. 'yellow'
  19. ,
  20. 'white'
  21. ,
  22. 'black'
  23. ];
  24. }
  25. Human
  26. .
  27. prototype
  28. .
  29. sayName
  30. =
  31. function
  32. (){
  33. return
  34. this
  35. .
  36. name
  37. ;
  38. }
  39. //call的用法和apply相同,只不过call在传递参数的时候需要把
  40. //参数一一列出,而apply传递的是arguments
  41. function
  42. Person
  43. (
  44. name
  45. ,
  46. age
  47. )
  48. {
  49. //构造函数继承属性
  50. Human
  51. .
  52. call
  53. (
  54. this
  55. ,
  56. name
  57. );
  58. this
  59. .
  60. age
  61. =
  62. age
  63. ;
  64. }
  65. //使用原型链继承方法
  66. Person
  67. .
  68. prototype
  69. =
  70. new
  71. Human
  72. ();
  73. Person
  74. .
  75. prototype
  76. .
  77. constructor
  78. =
  79. Person
  80. ;
  81. //定义自己的方法
  82. Person
  83. .
  84. prototype
  85. .
  86. sayAge
  87. =
  88. function
  89. (){
  90. return
  91. this
  92. .
  93. age
  94. ;
  95. }
  96. //测试使用
  97. var
  98. p1
  99. =
  100. new
  101. Person
  102. (
  103. 'Jack'
  104. ,
  105. 18
  106. );
  107. p1
  108. .
  109. colors
  110. .
  111. push
  112. (
  113. 'brone'
  114. );
  115. console
  116. .
  117. log
  118. (
  119. p1
  120. .
  121. colors
  122. );
  123. //["yellow", "white", "black", "brone"]
  124. console
  125. .
  126. log
  127. (
  128. p1
  129. .
  130. sayName
  131. (),
  132. p1
  133. .
  134. sayAge
  135. ());
  136. //Jack 18
  137. var
  138. p2
  139. =
  140. new
  141. Person
  142. (
  143. 'Rose'
  144. ,
  145. 20
  146. );
  147. console
  148. .
  149. log
  150. (
  151. p2
  152. .
  153. colors
  154. );
  155. //["yellow", "white", "black"]
  156. console
  157. .
  158. log
  159. (
  160. p2
  161. .
  162. sayName
  163. (),
  164. p2
  165. .
  166. sayAge
  167. ());
  168. //Rose 20

在这个例子中首先声明了一个 Human函数,然后使用原型模式定义了 sayName方法。声明了 Person函数,通过构造函数模式继承了 Human,但是这只能继承 Human中声明的属性,也就是实例属性,没办法继承原型属性和方法,于是紧接着将 Person的原型赋值为 Human的一个实例,通过原型链实现原型属性和方法的继承,然后添加了一个子类型特有的方法 sayName,这就是组合继承。测试使用,结果既可以通过构造函数向超类型传递参数,也没有 colors被共享的问题,同时还可以使用超类型原型上定义的方法 sayName,可以说非常完美。

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。而且,因为使用了原型链, instanceOf和 isPrototypeOf也能够识别基于组合继承创建的对象。