【js基础】继承

209 阅读6分钟

为什么要继承

代码的复用可以用函数实现,对象的复用就要用继承实现。

想要继承的效果

通过向构造函数传递参数实现实例对象属性的赋值,同时多个实例属性互不干扰。但是对象的方法可以共享,简单来说就是实现“属性不共享,方法共享”

原型三角关系

继承的方式

1. 原型链继承

-- 基本思想:让子类的原型对象指向父类的实例,这样通过prototype属性就能继承父类的属性和方法

问题一: 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;

问题二: 在创建子类型(例如创建Coder的实例)时,不能向超类型(例如Person)的构造函数中传递参数.

 <script>
      /**
       *
       * 原型链继承关键是将子构造函数的prototype赋值给new 父对象的实例
       * 即:Coder.prototype = new Person()
       *
       * 缺点:1.不能给父构造函数传递参数,
       *      2.如果修改子类继承来的引用类型属性,会影响其他子类继承的引用类型属性
       *
       **/
      //父构造函数
      function Person(name, inherit) {
        this.name = name;
        this.inherit = inherit;
        this.pinkColor = ["yellow", "white"];
        this.sayName = function () {
          console.log("my name is", this.name);
        };
        this.working = function () {
          return "父类的woking";
        };
      }
      //子构造函数
      function Coder(name) {
        this.name = name;
        this.age = 1;
        this.sayName = function () {
          console.log("my age is ", this.age);
        };
      }
      
      Coder.prototype = new Person();
      let shaohui = new Coder("shaohui");
      shaohui.pinkColor.push("black");

      console.log("shaohui.pinkColor:", shaohui.pinkColor);
      console.log("shaohui继承的属性inherit:", shaohui.inherit);
      console.log("shaohui.working:", shaohui.working());

      let duohui = new Coder("duohui");
      console.log("duohui.pinkColor:", duohui.pinkColor);
      console.log("duohui继承的属性inherit:", duohui.inherit);
      console.log("duohui.working:", duohui.working());
    </script>

运行结果及其说明:

2.借用构造函数继承

-- 基本思想:即在子类型构造函数的内部调用超类型构造函数.

问题一:方法都在构造函数中定义, 因此函数复用也就不可用了.

问题二:不能继承父类原型链上的属性和方法

 <script>
     <script>
      /**
       * 借用构造函数继承
       * 其实就是在子构造里面加了:父构造函数.call(this,父亲构造函数参数)
       *
       * 缺点:不能继承父类原型链上的属性和方法,
       * 无法实现函数复用,每个子构造函数都有父函数的副本,性能不好
       * **/
      //父构造函数
      function Person(name, inherit) {
        this.name = name;
        this.inherit = inherit;
        this.pinkColor = ["yellow", "white"];
        this.sayName = function () {
          console.log("my name is", this.name);
        };
      }
      //不能继承父类原型链上的属性和方法,
      // Person.prototype.working = () => {
      //   return "prototype.working";
      // };
      //子构造函数
      function Coder(name, age, inherit) {
        this.name = name;
        this.inherit = inherit;
        Person.call(this, this.name, this.inherit);
        this.age = age;
        this.sayName = function () {
          console.log("my age is ", this.age);
        };
      }
      let dalin = new Coder("dalin", 1, "dalin继承");
      dalin.pinkColor.push("black");
      console.log("dalin.pinkColor:", dalin.pinkColor);
      console.log("dalin.inherit:", dalin.inherit);
      // console.log("dalin.working:", dalin.working());

      let duohui = new Coder("duohui", 2, "duohui继承");
      console.log("duohui.pinkColor:", duohui.pinkColor);
      console.log("duohui.inherit:", duohui.inherit);
      // console.log("duohui.working:", duohui.working());
    </script>

运行结果及其说明:

3.借用构造函数原型继承

-- 基本思路: 使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承.

问题一:组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数: 一次是在创建子类型原型的时候(Son.prototype = new Super()),

另一次是在子类型构造函数内部(借用构造函数call). 寄生组合式继承就是为了降低调用父类构造函数的开销而出现的

 <script>
      /**
     * 借用构造函数+原型继承
     * 在子构造函数加上:父构造函数名.call(this,传参)
     * 再加:Person.call(this, this.name, this.inherit);  // 实现父类属性的继承
            Coder.prototype = new Person();      // 实现父类方法的继承
     * 
     *问题一:组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数: 
     一次是在创建子类型原型的时候(Coder.prototype = new Person()), 另一次是在子类型构造函数内部(借用构造函数call)
     **/

      //父构造函数
      function Person(name, inherit) {
        (this.name = name), (this.inherit = inherit);
        this.pinkColor = ["yellow", "white"];
        this.sayName = function () {
          console.log("my name is", this.name);
        };
        this.working = function () {
          return "父类的woking";
        };
      }
      //子构造函数
      function Coder(name, age, inherit) {
        this.name = name;
        this.inherit = inherit;
        Person.call(this, this.name, this.inherit); // 实现父类属性的继承
        this.age = this.age;
        this.sayName = function () {
          console.log("my age is ", this.age);
        };
      }

      Coder.prototype = new Person(); // 实现父类方法的继承

      let dalin = new Coder("dalin", 1, "dalin继承");
      console.log(dalin);
      console.log(dalin instanceof Coder);

      dalin.pinkColor.push("black");
      console.log("dalin.pinkColor:", dalin.pinkColor);
      console.log("dalin.inherit:", dalin.inherit);
      console.log("dalin.working:", dalin.working());

      let duohui = new Coder("duohui", 2, "duohui继承");
      console.log("duohui.pinkColor:", duohui.pinkColor);
      console.log("duohui.inherit:", duohui.inherit);
      console.log("duohui.working:", duohui.working());
    </script>

运行结果及其说明:

4.原型继承

为什么要原型继承--- 对已存在的对象不必重新创建属性

-- 基本思想:在object()函数内部, 先创建一个临时性的构造函数, 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例.

问题一: 当对象中包含引用类型值的原型时,该引用类型值会被所有子类实例共享;

问题二: 在创建子类型(例如创建Coder的实例)时,不能向超类型(例如Person)的构造函数中传递参数.

<script>
      /** 4.原型继承
       * 对已存在的对象不必重新创建属性
       *
       * 缺点:
       * 1.无法传参,
       * 2.如果修改子类继承来的引用类型属性,会影响其他子类继承的引用类型属性
       * **/

      const person = {
        name: "personName",
        age: "personAge",
        pinkColor: ["white", "yellow"],
        sayHi: () => {
          return "person say:hello";
        },
      };

      function object(obj) {
        function F() {}
        F.prototype = obj;
        return new F();
      }
      console.log("继承的pinkColor:", person.pinkColor);
      const shaohui = object(person);
      console.log(shaohui.sayHi());
      shaohui.pinkColor.push("black");
      console.log("shaohui.pinkColor:", shaohui.pinkColor);

      const duohui = object(person);
      console.log(duohui.sayHi());
      console.log("duohui.pinkColor:", duohui.pinkColor);
    </script>

运行结果及其说明:

5.寄生式继承

为什么要寄生式继承 --- 对原型式的增强版,子类可以自定义更多的属性和方法

-- 基本思想:在object()函数内部, 先创建一个临时性的构造函数, 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例. 再来个包装壳函数增强object函数里面的对象,最后返回增强版对象。

问题一: 当对象中包含引用类型值的原型时,该引用类型值会被所有子类实例共享;

问题二: 函数无法复用

 <script>
      /**
       * 5.寄生式继承是对原型式继承的增强版本
       *
       * 缺点:
       * 1.如果修改子类继承来的引用类型属性,会影响其他子类继承的引用类型属性
       * 2.无法复用函数
       *
       * **/
      const person = {
        name: "personName",
        age: "personAge",
        pinkColor: ["white", "yellow"],
        sayHi: () => {
          return "person say:hello";
        },
      };

      function object(obj) {
        function F() {}
        F.prototype = obj;
        return new F();
      }

      function enhanceObject(obj, otherName) {
        let clone = object(obj);
        clone.otherName = otherName;
        clone.sayOther = () => {
          return "我还会说其他的";
        };
        return clone;
      }
      let shaohui = enhanceObject(person, "shaohui");
      shaohui.pinkColor.push("black");
      console.log("shaohui.pinkColor:", shaohui.pinkColor);
      console.log("shaohui.sayOther:", shaohui.sayOther());
      console.log("shaohui.otherName:", shaohui.otherName);

      let duohui = enhanceObject(person, "duohui");
      console.log("duohui.pinkColor:", duohui.pinkColor);
      console.log("duohui.sayOther:", duohui.sayOther());
      console.log("duohui.otherName:", duohui.otherName);
    </script>
    

运行结果及其说明:

6.寄生组合继承

--基本思路:不必要指定子构造原型对象调用构造函数

<script>
      /***
       * 6.寄生组合继承
       * 解决:借用构造函数组合继承调用两次构造函数的问题
       * 思路:不必为了指定子类型的原型而调用超类型的构造函数
       * function extend(son, parent) {
       *let parentPrototype = object(parent.prototype);
       *parentPrototype.constructor = son;
       *son.prototype = parentPrototype;
       *}
       * **/
      //父构造函数
      function Person(name, inherit) {
        this.name = name;
        this.inherit = inherit;
        this.pinkColor = ["yellow", "white"];
        this.sayName = function () {
          console.log("my name is", this.name);
        };
      }
      Person.prototype.working = function () {
        return "父类的woking";
      };

      //子构造函数
      function Coder(name) {
        this.name = name;
        Person.call(this, this.name);
        this.sayName = function () {
          console.log("my age is ", this.age);
        };
      }

      function object(obj) {
        function F() {}
        F.prototype = obj;
        return new F();
      }

      function extend(son, parent) {
        let parentPrototype = object(parent.prototype);
        parentPrototype.constructor = son;
        son.prototype = parentPrototype;
      }

      extend(Coder, Person);

      const shaohui = new Coder("shaohui");
      console.log("shaohui.working:", shaohui.working());
      shaohui.pinkColor.push("black");
      console.log("shaohui.pinkColor:", shaohui.pinkColor);

      const duohui = new Coder("duohui");
      console.log("duohui.working:", duohui.working());
      console.log("duohui.pinkColor:", duohui.pinkColor);
    </script>

运行结果及其说明:

参考 JS原型链与继承别再被问倒了 javaScript的继承方案