JavaScript学习之继承

110 阅读4分钟

原型链继承

      function Person() {
        this.score = 100;
        this.teacher = ["wang"];
      }
      Person.prototype.age = 18;
      function Student(name) {
        this.name = name;
      }
      Student.prototype = new Person();
      var stu1 = new Student("lily");
      var stu2 = new Student("aa");
//---输出age-----------------------------------
      console.log(stu2.age);//18
      console.log(stu1.age);//18
//---更改age,然后输出--------------------------
      Person.prototype.age = 10;
      console.log(stu1.age);//10
      console.log(stu2.age);//10
//---更改父类的teacher--------------------------
      stu1.teacher.push("zhang");
      console.log(stu1.teacher);// ['wang','zhang']
      console.log(stu2.teacher);// ['wang','zhang']
//---实例对象继承了Person和Student--------------
      console.log(stu1 instanceof Person);//true
      console.log(stu1 instanceof Student);// true
  • 把父类的实例对象赋给子类构造函数的prototype
  • 特点:实例对象可以继承父类Person构造方法中的属性、父类原型对象上的属性。
  • 缺点:
    1. 无法向父类的构造函数传参
    2. 继承单一
    1. 所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

构造函数继承

盗用构造函数,这种方法相当于通过call、apply方法将父类的构造方法中的属性在实例对象上复制了一遍,这样保证了每个实例对象都有唯一的的父类构造方法中的属性(hobby,score),当一个实例对象中的hobby、score发生改变,其他实例对象不收影响的。

      function Person(hobby) {
        this.score = 100;
        this.hobby = hobby;
      }      
			function Student(name, hobby) {
        Person.call(this, hobby);
        this.name = name;
      }
      var stu1 = new Student("lily", "sing");
      var stu2 = new Student("aa", "dance");
      console.log(stu1.hobby);// sing
      console.log(stu2.hobby);// dance
      console.log(stu1.age); // undefined
      console.log(stu2.age); // undefined
      console.log(stu1.score);//100
      console.log(stu1 instanceof Person);// false
      console.log(stu1 instanceof Student);// true
  • 用call 改变Person 的 this 指向,此时this指向的当前的实例对象。只是改变指向的关系(类似于把Person中的属性赋制给了实例对象),此时实例并不会继承Person.prototype中的属性。
  • Person.prototype 并没在 实例对象的原型链上。
  • 特点:
    • 实例对象只继承Person构造方法中的属性。
    • 解决了原型链无法向父类构造方法传参、所用实例共享父类属性
    • 可以使用call() 让实例对象继承多个构造函数中的属性(不继承这些构造方法prototype中的属性)。
    • 在子类中可以向父类传参
  • 缺点:
    • 无法继承父类的prototype中的属性
    • 无法实现原型链上属性的复用。
    • 每个新实例都有父类构造函数的副本,臃肿。

组合继承(最经典的)

这种是构造方法继承和原型链继承结合。

思路:原型链上继承原型上的属性和方法。通过盗用构造函数让实例继承父类构造函数中的属性。

这样,既可以实现原型链上的属性复用,也可已让每个实例中的父类构造函数中的属性互相不污染。

      function Person(hobby) {
        this.score = 100;
        this.hobby = hobby;
        this.teacher = ["wang"];
      }
      Person.prototype.age = 18;     
			function Student(name, hobby) {
        Person.call(this, hobby);
        this.name = name;
      }
      Student.prototype = new Person();
      var stu1 = new Student("lily", "sing");
      console.log(stu1.hobby);// sing
      console.log(stu1.age); // 18
      stu1.teacher.push("zhang"); // ['wang']
      console.log(stu1.teacher); // ['wang','zhang']
      console.log(stu1 instanceof Person); // true
  • 结合了这两种方法的优点,并且这两种方法的缺点互补。
  • 特点:
    • 可以继承父类原型上的属性,可以传参,可复用
    • 每个新实例引入的构造函数属性是私有的。
  • 缺点:调用了两次父类构造函数(耗内存)

四、原型式继承

通过一个函数容器,里面创建一个构造函数,让这个构造函数继承Person实例。用容器封起来,这样所有实例中的属性互不影响。

      function Person(hobby) {
        this.score = 100;
        this.hobby = hobby;
        this.teacher = ["wang"];
      }
      Person.prototype.age = 18;     
			function Content(obj) {
        function fn1() {
          this.name = "lily";
        }
        fn1.prototype = obj;
        return new fn1();
      }
      const father = new Person("game");
      const stu1 =  Content(father);
      const stu2 =  Content(father);
      console.log(stu1);
      console.log(stu1.hobby);//game
      console.log(stu1.age);// 18
      stu2.hobby = "sing";
      console.log(stu2.hobby);//sing
      console.log(stu1.hobby);//game
      console.log(stu1 instanceof Person);//true
  • 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
  • 类似于复制一个对象,用函数来包装。
  • 缺点:
    • 所有实例都会继承原型上的属性
    • 无法实现复用。(新实例属性都是后面添加的)

五、寄生式继承

      function Content(obj) {
        function fn1() {}
        fn1.prototype = obj;
        return new fn1();
      }
      function createAnotherObj(originObj) {
        const obj = Content(originObj);
        obj.name = name;
        return obj;
      }
      var father = new Person("game");
      var stu1 = createAnotherObj(father, "lily");
      console.log(stu1);
      console.log(stu1 instanceof Person);// true

  • 重点:就是给原型式继承外面套了个壳子。
  • 优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
  • 缺点:没用到原型,无法复用。无法更改父类的属性

寄生组合式

组合继承其实存在效率问题,最主要的是:父类构造函数始终会被调用两次,第一次:创建子类原型是调用,另一次是在子类构造函数中调用。本质上子类原型中要包含超类(父类)对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。