阿西河

所有教程

公众号
🌙
阿西河前端的公众号

我的收藏

    最近访问  (文章)

      教程列表

      抓包专区
      测试专区

      JavaScript 继承

      继承的几种方法

      继承是面向对象语言中的一个最为人津津乐道的概念。许多面向对象都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在 ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

      继承的概念:子类继承父类中的属性和方法; 继承的目的:子类直接使用父类的方法,不需要再重写一遍; 继承的实际应用:B想有一个getX的方法,我们发现A中已经有了,我接下来只需要让B作为儿子,让A作为父亲,让B继承A中的属性和方法;

      • 知识点一:原型继承
      • 知识点二:call继承
      • 知识点三:call加原型继承(推荐)
      • 知识点四:冒充对象继承
      • 知识点五:中间类继承

      知识点一、原型继承

      原型继承的原理:B要继承A,只需要让B的原型等于A的一个实例即可 ->B.prototype=new A;

      function A() {
        this.x = 100;
      }
      A.prototype.getX = function () {
        console.log(this.x);
      };
      function B() {
        this.test="test"
      }
      B.prototype.getY = function () {
        console.log("B.prototype");
      };
      B.prototype = new A;
      var b = new B;
      console.dir(b);
      b.x=200;
      b.getX();//200;
      A.prototype.getX();//undefined
      //子类重写父类的方法
      b.__proto__.__proto__.getX = function () {
          console.log(this.x + 1000);
      };
      b.getX();//1200;
      

      1)原型继承是我们JS中最常用的一种继承方式,属于改写原生的原型,指向需要继承的实例;;

      2)原型继承不是把父类的属性和方法复制一份给自己,而是更改了原型链的查找顺序,我们b想要使用getX这个方法,是通过__proto__一级级的向上查找,最后也找到A的原型上,获取getX并且使用 -> b.proto.proto.getX=A.prototype.getX

      3)子类不仅仅可以获取使用父类上的方法,而且还可以把父类原型上的方法进行修改 b.proto.proto.getX=function(){console.log(this.x+1000);} 就是把父类A原型上的方法修改了,这样的话,A的其他的实例也会受到影响 ->“这叫做类的多态(是多态中的重写):子类重写父类的方法”

      4)在原型继承中,子类可以把父类中私有和公有的属性/方法都继承过来使用;并且都是作为子类B公有的属性/方法;(因为B每次查找都经过A的实例,所以A中的私有方法和属性,会被B所继承)

      原型继承的应用;

      写一个方法,求评委打分的平均分(去掉一个最高分,去掉一个最低分)

      function fn() {
          arguments.__proto__ = Array.prototype;
          arguments.sort(function (a, b) {
              return a - b;
          });
      
          arguments.shift();//去掉最高分
          arguments.pop();//去掉最低分
          console.log(eval(arguments.join("+")) / arguments.length);
          console.log(arguments.join("+") , arguments.length);
      }
      
      fn(56,67,78,89,90,12,23,34 ,45);
      fn(56, 67, 78, 89, 90, 12, 23, 34, 45 , 45,14,34,67);
      

      原型继承的缺点:

      function SuperType(){
          this.colors = ["red", "blue", "green"];
      }
      function SubType(){}
      
      //继承了 SuperType
      SubType.prototype = new SuperType();
      
      var instance1 = new SubType();
      instance1.colors.push("black");
      console.log(instance1.colors); //"red,blue,green,black"
      
      var instance2 = new SubType();
      console.log(instance2.colors); //"red,blue,green,black"
      

      1、完全继承了A,自己原型上的方法被吃掉了;杀敌1000,自损800的做法; 2、最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。(所有属性都是大家伙的了) 3、由第二个问题导致的就是,在创建子类的实例时,不能向父类的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

      所以引出了call继承(apply同样的原理);

      知识点二、call继承(apply同样的原理,借用构造函数/伪造对象/经典继承)

      call继承的原理:在子类B的私有属性中,把父类A当做一个普通的函数执行,并且把里面的this变为子类B的一个实例b,这样的话,在执行A的时候,A中写的this.xxx=xxx都相当于给b增加的私有的属性

      function A() {
          this.x = 100;//b.x=100 给b增加一个私有的属性x=100
          this.flagA="flagA"
      }
      A.prototype.getX = function () {
          console.log(this.x);
      };
      function B() {
          //this->b
          //A() ->作为一个普通的函数执行,this->window,A的原型是起不到作用的
          A.call(this);//->执行A函数,把A里面的this变为b
          this.flagB="flagB"
      }
      B.prototype.getBPrototype = function () {
          console.log("B.prototype");
      };
      
      var b = new B;
      console.log(b);
      

      1)call继承首先只能继承父类A中的私有的属性(this.xxx=xxx),并且和原型不一样,call继承是把A中的私有的属性复制一份直接的给了B的实例

      2)把父类A中的私有属性继承到子类B中的私有的属性

      b的结构如下;

      call继承的优点

      对实例1的操作不会影响实例2,

      function SuperType(){
          this.colors = ["red", "blue", "green"];
      }
      function SubType(){
      // 继承了 SuperType
      SuperType.call(this);
      }
      var instance1 = new SubType();
      instance1.colors.push("black");
      console.log(instance1.colors); //"red,blue,green,black"
      var instance2 = new SubType();
      console.log(instance2.colors); //"red,blue,green"
      

      call继承的缺点

      只继承了A的私有属性,没有继承A的原型; 如果仅仅是call继承,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在父类的原型中定义的方法,对子类而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。

      所以引出了call继承+原型继承;

      知识点三、call继承加原型继承(也叫做伪经典继承)

      call继承+原型继承

      function A() {
          this.x = 100;
          this.flagA="flagA"
      }
      A.prototype.getX = function () {
          console.log(this.x);
      };
      function B() {
          A.call(this);
          this.flagB="flagB"
      }
      B.prototype.getBPrototype = function () {
          console.log("B.prototype");
      };
      
      
      B.prototype = new A;
      var b = new B;
      
      console.log(b);
      

      call继承加原型继承解决的事情

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

      function SuperType(name){
          this.name = name;
          this.colors = ["red", "blue", "green"];
      }
      SuperType.prototype.sayName = function(){
          console.log(this.name);
      };
      function SubType(name, age){
          //继承属性
          SuperType.call(this, name);
          this.age = age;
      }
      //继承方法
      SubType.prototype = new SuperType();
      SubType.prototype.constructor = SubType;//constructor 需要写回去,否则SubType虽然继承了SuperType的私有属性和公有属性,但是SubType原来的原型被重写了;
      SubType.prototype.sayAge = function(){
          console.log(this.age);
      };
      var instance1 = new SubType("Nicholas", 29);
      instance1.colors.push("black");
      console.log(instance1.colors); //"red,blue,green,black"
      instance1.sayName(); //"Nicholas";
      instance1.sayAge(); //29
      
      var instance2 = new SubType("Greg", 27);
      console.log(instance2.colors); //"red,blue,green"
      instance2.sayName(); //"Greg";
      instance2.sayAge(); //27
      

      用这种模式一定要注意原型的constructor重写会子类,否则SubType虽然继承了SuperType的私有属性和公有属性,但是SubType原来的原型被重写了;

      知识点四、冒充对象继承(call继承的升级版)

      冒充对象继承:在子类B的私有函数体中,创建一个父类A的实例temp(temp中就存有父类A中的私有和公有的属性/方法),我们把temp当做一个普通的对象进行遍历,分别把temp中存有的属性都存储到我们子类B的私有中一份

      function A() {
          this.x = 100;
          this.flagA="flagA"
      }
      A.prototype.getX = function () {
          console.log(this.x);
      };
      function B() {
          // A.call(this);
          this.flagB="flagB"
      
          var temp = new A;//->temp中现在就存有了A中的私有和公有的属性/方法
        for (var key in temp) {
            this[key] = temp[key];
        }
        temp = null;
      }
      B.prototype.getBPrototype = function () {
          console.log("B.prototype");
      };
      
      var b = new B;
      console.log(b);
      
      • 1)、冒充对象继承首先继承父类中的私有和公有的属性/方法,并且和原型不一样,冒充对象继承是把A中的私有的属性和共公有的方法复制一份直接的给了B的实例
      • 2)、把父类A中的私有属性和共公有的方法继承到子类B中的私有的属性

      备注:temp只是一个借用的对象,所以用完记得置为null,类似数组排序中的借用的那个对象,仅仅只是内部处理所需的,没有实际的意义;

      test

      目录
      目录