重点分析四个常用的继承方式,并且由最次到最佳的递进过程,分析每个继承的优缺点。

原型继承

主要利用JavaScript的原型链。
代码演示

  1. function Parent(sex){
  2. this.sex = sex
  3. }
  4. Parent.prototype.setSex = function(){}
  5. function Son(name){
  6. this.name = name
  7. }
  8. Son.prototype = new Parent()
  9. var s1 = new Son("bobo")
  10. console.log(s1)

结果打印:
原型继承.png

关键实现

把子类的原型指向父类的实例,完成了子继承父类的私有属性和原型属性。

优点:

  • 父类新增的原型属性和方法,子类实例都能访问到
  • 简单、易用

    缺点:

  • 无法实现多继承,一个子类继承多个父类

  • 创建子类实例的时候,无法向父类构造函数传参
  • 存在子类实例共享父类引用属性的问题(子类的原型同时指向父类的一个实例,假如父类的私有属性是一个引用类型,那么任何一个子类操作该引用类型属性的时,会导致其他子类使用的这个属性发生变化)

    借用父类构造函数继承

    代码实例

    1. function Parent(sex){
    2. this.sex = sex
    3. }
    4. Parent.prototype.setSex = function (){}
    5. function Son(name, age, sex){
    6. Parent.call(this, sex)
    7. this.name = name
    8. this.age = age
    9. }
    10. var son = new Son("bobo",18,'male')
    11. console.log(son)

    输出结果
    混合继承.png

    关键实现:

    在子类构造函数中使用call或者apply调用父类的构造函数,继承父类私有属性。

    优点:

  • 创建子类实例时,可以向父类传递参数

  • 可以实现多继承
  • 解决原型继承中子类实例共享父类引用类型属性的问题。(在创建子类实例时,都会重新调用父类构造函数重新创建一个引用类型属性的数据)

    缺点:

  • 每次创建子类实例,都要调用一次父类构造函数,影响性能

  • 只继承了父类的私有属性,没有继承父类的原型属性

    组合式继承(原型链+借用构造函数)

    代码实例: ```javascript function Parent(sex){ this.sex = sex } Parent.prototype.setSex = function(){}

function Son(name, age, sex){ Parent.call(this, sex) this.name = name this.age = age } Son.prototype = Object.create(Parent.prototype)

// 修正Son的构造函数 Son.prototype.constructor = Son var son = new Son(“bobo”, 18, “boy”) console.log(son)

  1. 执行结果:<br />![混合继承.png](https://cdn.nlark.com/yuque/0/2020/png/737887/1599752624967-68a9f598-355e-4941-8677-354f507babb8.png#align=left&display=inline&height=468&margin=%5Bobject%20Object%5D&name=%E6%B7%B7%E5%90%88%E7%BB%A7%E6%89%BF.png&originHeight=468&originWidth=484&size=26771&status=done&style=none&width=484)
  2. <a name="AH5Ks"></a>
  3. ### 关键实现:
  4. 通过调用父类构造函数,继承父类的属性并保留传参的优点,通过Object.create(Parent.prototype)来继承父类原型属性的对象,并把这个对象赋值给子类的原型。<br />既能保证父类构造函数不用执行两次,又能让子类能继承到父类的原型方法。
  5. <a name="9eBZY"></a>
  6. ### 优点:
  7. - 创建子类实例时,可以向父类传递参数
  8. - 可以实现多继承
  9. - 解决了原型链继承中子类实例共享父类是引用类型属性的问题。
  10. - 父类构造函数只执行一次。<br />
  11. <a name="4dSvY"></a>
  12. ## Es6中class的继承
  13. ES6中引入了**class关键字**,class可以通过extends关键字实现继承,还可以通过**static**关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要**清晰和方便**很多。<br />**注意:ES5 的继承**,实质是**先创造子类的实例对象this**,然后**再将父类的方法添加到this**上面(Parent.apply(this))。<br />**ES6 的继承机制完全不同**,实质是**先将父类实例对象的属性和方法加到this上面(所以必须先调用super方法)**,**然后再用子类的构造函数修改this**。<br />代码实例:
  14. ```javascript
  15. class A{
  16. constructor(sex){
  17. this.sex = sex
  18. }
  19. showSex(){
  20. console.log("父类中的方法")
  21. }
  22. }
  23. class B extends A{
  24. constructor(name, age, sex){
  25. super(sex);
  26. this.name = name;
  27. this.age =age;
  28. }
  29. showSex(){
  30. console.log("这里是子类的方法")
  31. }
  32. }
  33. let b = new B("bbb",12,"boy")
  34. console.log(b)

执行结果
calss.png

关键实现原理

使用extends关键字继承父类的原型属性,调用super来继承父类的实例属性,并且保留向父类构造函数传参的优点

优点:

简单易用,书写方便。不用自己来修改原型链完成继承

接下来将代码从ES6编译到ES5来看看到底class继承的代码最终会被编译成什么样
111.png
从上图分析得到:

  • 上述代码示例中的super指的就是父类构造函数
  • 子类继承父类的实例属性最终还是通过call或者apply来实现继承的
  • 通过extends方法的调用来修改子类和父类的原型链关系

再看经过编译后的extends方法,如下
222.png
1、注意Object.setPrototypeOf()方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null
2、(.prototype = b.prototype, new ())表达式的执行执行顺序是先执行前者,再返回后者
从上图可知,extends做了以下几件事:

  • 定义了一个function __() {}函数,并把该函数的constructor指向了子类
  • 紧接着,把function __() {} 函数的原型指向了父类的原型
  • 最后再把function () {} 函数的实例赋给了子类函数,就这样子类的实例就能沿着proto.proto获取到父类的原型属性了,这种继承模式俗称圣杯模式

    基于class实现简版Jquery

    1. class Jquery{
    2. constructor(selector){
    3. const selectList = document.querySelectorAll(selector)
    4. let length = selectList.length
    5. for(let i = 0;i< length;i++){
    6. this[i] = selectList[i]
    7. }
    8. this.length = length
    9. }
    10. getIndex(index){
    11. return this[index]
    12. }
    13. each(fn){
    14. for(let i=0;i<this.length; i++){
    15. fn(this[i])
    16. }
    17. }
    18. on(type, fn){
    19. return this.each(elem=>{
    20. elem.addEventListener(type, fn, false)
    21. })
    22. }
    23. // ......
    24. }
    25. // 扩展插件
    26. Jquery.prototype.dialog = function(str){
    27. alert(str)
    28. }
    29. // 基于Jquery扩展处理Xquery
    30. class Xquery extends Jquery{
    31. constructor(selector){
    32. super(selector)
    33. }
    34. // 扩展自己的方法
    35. addClass(){}
    36. getClass(){}
    37. }