js中的继承

591 阅读3分钟

为何要继承

希望子类对象拥有父类的属性和方法

构造函数的继承

call写在子类构造函数中,将父构造函数中this指向由window指向子对象,并且执行父构造函数中的代码,(其实就相当于将父构造函数中的代码复制一份到子类构造函数中,将整个看做是子类构造器中的内容),所以改变了this指向,也继承了父类构造函数中的代码

    function People(){
      this.legs = 2;
    }
    function Man(){
      People.call(this);
      this.run = 3;
    }
    let m1 = new Man();

call方法和apply方法的区别:从第二个参数起,call直接将这些参数依次传递给父构造函数,apply将这些参数放入数组中在传递给父构造函数。

   function People(name,legs){
      this.name = name;
      this.legs = legs;
    }
    function Man(name,legs){
      People.call(this,name,legs);
      //People.apply(this,[name,legs]);
      this.run = 3;
    }
    let m1 = new Man('yr', 2);

注意: call和apply继承的只是构造函数中的属性和方法,通过原型prototype给父类添加的是无法继承的。

原型继承1:子类prototype直接引用父类prototype

   function People(name,legs){
      this.name = name;
      this.legs = legs;
    }
    People.prototype.showName = function(){
      console.log(this.name)
    }
    function Man(){
      this.run = 3;
    }
    Man.prototype = People.prototype;
    Man.prototype.showLegs= function(){
      console.log(this.legs)
    }
    let m1 = new Man('yr', 2);

这种原型继承有个问题:就是直接将子类的prototype指向父类的prototype,这时他们两个使用的用一个内存地址,我们在子类Man的prototype上添加的showLegs也会添加在父类People的prototype上,显然这种方式是不合适的。

原型继承2:拷贝继承

   function People(name,legs){
      this.name = name;
      this.legs = legs;
    }
    People.prototype.showName = function(){
      console.log(this.name)
    }
    function Man(name,legs){
      People.call(this,name,legs)
      this.run = 3;
    }
    Man.prototype.showName= function(){
      console.log(this.legs)
    } 

   Man.prototype = {
      ...People.prototype,
      ...Man.prototype
    }
   
    let m1 = new Man('yr',18);
    let m2 = new Man('lyl',20);

拷贝继承,使用扩展运算符将父类原型与子类原型合并,属性相同时,子类为准 (扩展运算符属性相同时,后面的覆盖前面的)

原型继承3:继承父类实例化对象

   function People(name,legs){
      this.name = 'yr';
      this.legs = 2;
    }
    People.prototype.showName = function(){
      console.log(this.name)
    }
    function Man(){
      this.run = 3;
    }
    Man.prototype = new People();
    Man.prototype.showLegs= function(){
      console.log(this.legs)
    }
    let m1 = new Man();

这种子类继承了父类构造函数实例化对象,可以获得父类构造函数和原型上的属性和方法。但是存在一个问题,如果父类构造函数中包含引用类型值(数组,对象那类),就会出现以下问题

People的每个实例都有name属性,Man.prototype = new People();就相当于Man.prototype成为了People的一个实例,就像是创建了Man.prototype.name,那么通过Man创建出来的实例都会共享这个属性。

   function People(name,legs){
      this.names = ['yr','lyl'];
      this.legs = 2;
    }
    People.prototype.showName = function(){
      console.log(this.name)
    }
    function Man(){
      this.run = 3;
    }
    Man.prototype = new People(); 
    Man.prototype.showLegs= function(){
      console.log(this.legs)
    }
    let m1 = new Man();
    m1.names.push = 'xn';

    let m2 = new Man();

此时我们需要的m2是不应该有新添加的xn,并且这种继承不能像父类构造函数传参

组合式继承

通过call或者apply继承构造函数,prototype继承父级的prototype

    function People(name,legs){
      this.name = name;
      this.legs = legs;
    }
    People.prototype.showName = function(){
      console.log(this.name)
    }
    function Man(name,legs){
      People.call(this,name,legs)
      this.run = 3;
    }
    Man.prototype = new People();
    Man.prototype.constructor = Man;
    Man.prototype.showLegs= function(){
      console.log(this.legs)
    }
    let m1 = new Man('yr',18);
    let m2 = new Man('lyl',20);

组合式继承的原型链是

总结

当子类的prototype继承父类实例化对象时,原型链才发生真正的变化。

当我们使用某一个对象的属性和方法时,会先在创建对象的构造函数中查找自身是否拥有此属性,如果没有,会到构造函数的原型对象中查找,如果还是没有,会继续向继承父级查找,找父类原型中查找,然后就是Object基类中查找,直到找到null,但是返回的是undefined 。