前端系统化学习【JS篇】:(十八)继承

918 阅读6分钟

前言

  • 细阅此文章大概需要 6分钟\color{red}{6分钟}左右
  • 本篇中讲述了:
      1. JS中的继承
      1. 第一种继承方案:【原型继承】
      1. 第二种继承方案:【CALL继承】
      1. 第三种继承方案:【寄生组合式继承】
      1. ES6中实现的类和继承
  • 如果有任何问题都可以留言给我,我看到了就会回复,如果我解决不了也可以一起探讨、学习。如果认为有任何错误都还请您不吝赐教,帮我指正,在下万分感谢。希望今后能和大家共同学习、进步。
  • 下一篇会尽快更新,已经写好的文章也会在今后随着理解加深或者加入一些图解而断断续续的进行修改。
  • 如果觉得这篇文章对您有帮助,还请点个赞支持一下,谢谢大家!
  • 欢迎转载,注明出处即可。

JS中的继承

  • JS本身就是基于面向对象开发的编程语言
    • 同样实现了=>类:封装、继承、多态
  • 封装:类也是一个函数,把实现一个功能的代码进行封装,以此实现“低耦合高内聚”
  • 多态:重载、重写【因为javascript是弱类型语言,没有严格意义上的多态】
    • 重写:子类重写父类上的方法(伴随着继承运行的)
    • 重载:相同的方法,由于参数或者返回值的不同,具备了不同的功能(JS中不具备严格意义上的重载)【JS中的重载:是在同一个方法内,根据传参的不同实现不同的功能】
  • 继承:子类继承父类当中的方法
    • 目的是为了让子类的实例也能同时具备父类中的私有属性和公共方法
    • 【在JS中的继承和其它语言的继承是不太一样的,是 基于原型和原型链的查找式的继承】【所以会导致重写的影响会很大】

JS中的第一种继承方案:【原型继承】【子类constructor和子类的公共方法将丢失】

  • 原型继承:让子类的原型等于父类的实例即可

  • 原型继承的几大特点:

    1. 父类中私有的和公有的属性方法,最后都变为子类实例公有的
    2. 【与其它语言的继承不同的是】原型继承并不会把父类的属性方法“拷贝”给子类,而是让子类实例基于__proto__原型链查找到自己定义的属性和方法 【是“指向/查找”式的,而不是拷贝式的】
      • 而此时若修改子类原型(已经变为父类的一个实例)中的内容 【[子类实例].proto.xxx = xxx】,内容被修改后,对子类的其他实例有影响,但是对父类的实例不会有影响。
      • 而此时 若修改父类原型中的内容 【[子类实例].proto.proto.xxx = xxx】,这样不仅会影响其他父类的实例,也会影响其他子类的实例

  • 【原型继承】实现了 【父类私有的公有的都变成子类实例公有的】

  • 但我们却更希望 【父类私有的变成子类实例私有的,父类公有的变为子类实例公有的】


JS中的第二种继承方案:【CALL继承】【只能继承父类中私有的,不能继承父类中公有的】

  • 在子类的构造函数中,把父类当作普通函数,并用【CALL修改其this指向为子类】执行,(没有父类实例,父类原型上的东西也就和子类实例没关系了)
  • 这种做法相当于 强制给子类的实例设置一个父类的私有属性,变相等于让子类实例继承了父类的私有属性,这种继承方式是“拷贝式”
     function Parent(){
         this.x = 100;
     }
     Parent.prototype.getX = function(){
         return this.x;
     };
     function Child(){
         // 在子类的构造函数中,把父类当作普通函数,并用【CALL修改其this指向为子类】执行,
         //(没有父类实例,父类原型上的东西也就和子类实例没关系了)
         //在构造函数执行时this->Child的实例c1
         Parent.call(this);//=>this.x = 100;
         // 这种做法相当于 强制给子类的实例设置一个父类的私有属性,
         //变相等于让子类实例继承了父类的私有属性,这种继承方式是“拷贝式”的
         this.y = 200;
     }
     Child.prototype.getY = function(){
         return this.y;
     };
     let c1 = new Child;
     console.log(c1); // Child {x: 100, y: 200}

  • 【CALL继承】依然不能实现 【父类私有的变成子类实例私有的,父类公有的变为子类实例公有的】


【最好】JS中的第三种继承方案:【寄生组合式继承】【CALL继承+原型重定向】【父类私有的变成子类实例私有的,父类公有的变为子类实例公有的】

  • 在保留原有CALL继承【父类私有的变成子类实例私有的】的基础之上,利用原型重定向,【实现将父类公有的方法变为子类实例公有的】
  • 但是 【原型重定向,修改proto原型链指向】 在IE低版本浏览器中不支持,【需要配合Object.create和Object.assign来实现修改原型链指向这一操作】
    1. 【Object.create】先创建一个空对象,让其原型链指向我们指定的原型对象

    2. 【Object.assign】在使用该方法,让子类的原型对象和这个空对象合并

    3. 【结果】从而保留了子类的原型对象上的【constructor】和【子类公共的属性方法】,并且将子类原型对象的原型链指向变为了父类的原型对象,从而父类公有的变为子类实例公有的。【而且兼容低版本浏览器】【配合着CALL也能让子类实例继承父类的私有属性】

   function Parent(){
       this.x = 100;
   }
   Parent.prototype.getX = function(){
       return this.x;
   };
   function Child(){
       Parent.call(this);
       this.y = 200;
   }
   // Child.prototype.__proto__ = Parent.prototype;//【修改proto原型链指向】在IE低版本浏览器中不支持
   let A = Object.create(Parent.prototype);
   //【Object.create】先创建一个空对象,让其原型链指向我们指定的原型对象
   Child.prototype = Object.assign(A,Child.prototype);
   //【Object.assign】在使用该方法,让子类的原型对象和这个空对象合并
   // 此时子类原型对象的原型链指向变为了父类的原型对象
   Child.prototype.getY = function(){
       return this.y;
   };
   let c1 = new Child;
   console.log(c1); //  Child {x: 100, y: 200}
   console.log(c1.__proto__); //{getY: ƒ} 子类原型上的方法
   console.log(c1.__proto__.__proto__);// {getX: ƒ, constructor: ƒ} 父类原型上的方法 和constructor

ES6中类和继承【也不错】

  • ES6中使用class来创建类:其中constructor函数中写入的是实例私有的方法和属性,外部写的都是加入到原型上的公共方法和属性
  • 【注意】:使用class创建的类无法当作普通函数执行,只能new执行
  • 继承extend 类名【实现的效果类似于寄生组合继承】
    • 继承后一定要在子类的construtcor中加super();【super的效果类似于之前的CALL】
   class Parent{
       constructor(){
           this.x = 100;
       }
       //相当于Parent.prototype.getX = function(){...}
       getX (){
       return this.x;
   };
   }
   class Child extends Parent{
       constructor(){
           super();//super的效果类似于之前的CALL
           //super(100,200);//相当于把Parent中的constructor执行,传递了100和200,给子类的实例创建了属性
           this.y = 200;
       }
       //相当于Parent.prototype.getY = function(){...}
       getY (){
       return this.y;
   	};
   }