JS继承(原型链、组合、构造函数、calss、Extends)

330 阅读5分钟

为什么要继承?

比如: 医生是人类(Person)有年龄和名字,但是每个医生(Doctor)的职称又不相同,我们总不能给每一个医生都当做单独的一个对象创建出来吧?,所以我们可以用继承来试试

原型链继承

实质:就是让子类(Doctor)的原型对象等价于父类(Person)的实例对象

例子

 //目的:让Doctor继承Person的属性和方法
    //第一步, 构造Person函数
    function Person(name,age){
      this.name=name;
      this.age=age;
    }
    // 构造Doctor函数
    function Doctor(jobTitle,pay){
      this.jobTitle=jobTitle;1
      this.pay=pay;
    }
    //第二步,将子类的原型对象[Doctor.prototype] 修改为 父类的实例对象[方法:  new person()  ] 
    Doctor.prototype=new Person("张医生",49);//注意:这里的参数  张医生 49 一旦改变会影响所有继承者的数据
    // D1,D2为Doctor的实例
    var D1=new Doctor("外科主任",12000)
    console.log(D1)
    var D2=new Doctor("妇科主任",11000)
    console.log(D2);

从上面的例子我们能够发现原生链继承的不足

  • 原型链继承时如果我们让多个子类(Doctor)继承父类(Person)的方法时,由于引用的属性指向相同,如果我们改变父类实例的属性传入的属性时会让所有继承者的数据都改变
  • 子类实例的原型对象的引用地址和父类实例的引用地址相同
  • 由于是利用子类原型对象继承,所以不能给他传参数,如果要传参就会回像我们第一条说的那样“牵一发而动全身”

借用自定义构造函数继承

实质:将我们要继承的属性或方法定义在父类的自定义构造函数中(简单说就是函数套函数)。在子类自定义函数中用普通方式调用父类的自定义函数(注意调用时候this指向是要改变一下的)

例子

function Person(name, age) {
      this.name = name;
      this.age = age;
      this.emotion = ['喜', '怒', '哀', '乐'];
      this.eat = function () {
        console.log("猜猜我吃的啥!");
      }
    }
    function Doctor(name, age, pay) {
      //这里调用了父类的构造函数,下面会不会出现问题1呢?
      Person.call(this, name, age);
      this.pay = pay;
    }
    var D1 = new Doctor("王老师", 25, 5000);
    var D2 = new Doctor("韩老师", 22, 4500);
    //可以发现,我们并没有给D1、D2传递情绪emotion的参数,但是D1和D2确都继承了情绪emotion
    console.log(D1);
    console.log(D2);
    console.log(D1.eat == D2.eat);//说明他两个的eat并非一样的,解决了原生继承中指向单一问题,但是我们可以发现是不是单独为每个都开辟了空间呢?

从上面我们可以发现借用构造函数继承也有不足

  • 继承的方法只能定义在父类的自定义函数中,不能定义在父类实例的原型对象上
  • 在调用父类函数时我们用了普通方式调用并改变了this指向,让其指向父类的自定义函数;所以继承者会继承他身上的自带属性(上边说的问题1)
  • 继承来的函数无法复用,虽然满足了我们个性化需求但是占内存

组合继承

实质:结合原生继承和借用构造函数继承的优点来完成继承,实现步骤这里我就忽略不说了,哈哈哈😂

Inkedbfeb7bd1ee3643feaf55f50d4ad5e350_tplv-k3u1fbpfcp-watermark_LI.jpg

例子

 function Person(name, age) {
      this.name = name;
      this.age = age;
      this.emotion = ['喜', '怒', '哀', '乐'];
    }
    //将父类的方法定义在父类的原型对象上。
    Person.prototype.eat = function () {
      console.log("你猜猜我吃的啥!");
    }
    function Doctor(name, age, pay) {
      //在子类的构造函数中借用父类的构造函数
      Person.call(this, name, age);
      this.pay = pay;
    }
    -------------------------------------------------------------------------
    //将子类的原型对象指向 父类的实例对象,想一想为什么?
    Doctor.prototype = new Person();
    -------------------------------------------------------------------------
    // 修正constructor属性
    Doctor.prototype.constructor = Doctor;
    console.log(Doctor.prototype.constructor === Doctor);
    var D1 = new Doctor("王老师", 25, 5000);
    console.log(D1.name); // 可以继承父类的属性
    D1.eat();//可以继承 父类原型对象上的方法。
    D1.emotion.push("愁");//D1添加一个愁的情绪,并没有影响D2
    var D2 = new Doctor("韩老师", 22, 4500);
    console.log(D2.emotion);
    console.log(D1); 

注意:Doctor实例的原型对象已经等价为Person的实例对象了(原生链继承),而 Person的实例对象是没有constructor属性的,所以,你猜猜下面的代码会输出啥结果?

console.log(Doctor.prototype.constructor === Doctor);

正常的理解,上面的代码,Doctor的原型对象的constructor不就是指向Doctor么?由于原生链继承导致的,Doctor实例的原型对象已经等价为Person的实例对象了,所以我们需要修正一下Doctor的constructor属性。

尽管组合继承解决了我们很多问题,但是!!我们也发现了组合继承的不足和优点

  • 优点:既可以继承到父类的原型中的方法,也可以传参
  • 缺点:父类中的实例兑现的属性,方法即存在子类的实例对象中,也存在于子类实例的原型对象中,占内存,总之就是继承了以上两种的优点和缺点😂

Es6实现继承

介绍Es6继承之前,我们先来了解一个新的关键字class,ES6的class是一个语法糖。

//之前自定义函数的写法
      function Cat(name,color){
          this.name = name;
          this.color = color;
        }
        Cat.prototype.eat = function(){
          console.log("吃老鼠");
        }
//用Class来定义个函数
      class Cat{
      //构造器
      constructor(name,color){
        this.name = name;
        this.color = color;
      }
      // 在这里定义的方法就会在原型对象上
      eat(){
        console.log("吃老鼠");
      }
    }

-----------------------Es6实现继承需要使用extendssuper关键字----------------------

 class Person{
      constructor(type){
        this.type = type;
      }
      Skill(){
        console.log("会独立思考");
      }
    }
    class Doctor extends Person{
      constructor(name,age){
        //相当于调用父类的构造器
        super("哺乳纲智人种")
        this.name = name;
        this.age = age;
        
      }
      eat(){
        console.log("你猜猜我吃的啥!");
      }
    }
    // Doctor的实例D1
    var D1 = new Doctor("张医生",30);
    console.log(D1);
    console.log(D1.Skill);//获取到了Person实例的原型对象上的Skill
    console.log(D1.name);
    console.log(D1.age);
    D1.eat();
    console.log(D1.type);//继承了Person自带的type属性
    D1.eat();

注意:super必须在子类(Doctor)的this之前调用,如果在之后调用则会报错。

微信截图_20211122184602.png 注意:supper当对象来调用的时候代表父类(Person)的原型对象