简单易懂的JS继承图解

我们先创建一个父类

复制代码

  // 父类

  function Animal(name, color){

      this.name = name;

      this.attribute = {

          color: color,

      }

      this.action = function (currentAction) {

          console.log(this.name + currentAction)

      }

  }

复制代码

原型链继承

实现

原理:将父类的实例作为子类的原型

    function OrangeCat(){};

    OrangeCat.prototype = new Animal('橘猫','橘色');

        // 相当于OrangeCat.prototype.__proto__ = new Animal('橘猫','橘色').__proto__;

        // __proto__是系统变量,可以省略

    let firstOrangeCat = new OrangeCat();

缺陷

缺少constructor,需要手动添加

引用类型的属性被所有子类实例共享

子类实例化时无法向父类构造函数传参

缺少constructor

我们直接打印一下OrangeCat,会发现缺少constructor,我们可以使用OrangeCat.prototype.constructor手动添加上constructor

image.png

引用类型的属性被所有子类实例共享

让我们来看一下下面的例子

复制代码

    function OrangeCat(){}

    OrangeCat.prototype = new Animal('橘猫','橘色');

    // 第一只橘猫

    let firstOrangeCat = new OrangeCat();

    // 第二只橘猫

    let secondOrangeCat = new OrangeCat();

    console.log('第一只橘猫的颜色:' + firstOrangeCat.attribute.color);

    console.log('第二只橘猫的颜色:' + secondOrangeCat.attribute.color);

    // 将第一只橘猫的颜色改为黑色

    firstOrangeCat.attribute.color = 'black';

    console.log('颜色改变后第一只橘猫的颜色:' + firstOrangeCat.attribute.color);

    console.log('颜色改变后第二只橘猫的颜色:' + secondOrangeCat.attribute.color);

复制代码

结果:

image.png

图解

image.png

借用构造函数继承

实现

原理: 使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型),可以实现多继承(call多个父类对象)

  function YellowDog(name, color) {

      Animal.call(this, name, color);

  }

  let firstYellowDog = new YellowDog('狗', '黄');

缺陷

只能继承父类的实例属性和方法,不能继承原型属性/方法

  console.log(firstYellowDog instanceof Animal); // false

  console.log(firstYellowDog instanceof YellowDog); // true

无法实现复用,每个子类都有父类实例函数的副本,影响性能

图解

新创建一个BlackDog子类

    function BlackDog(name, color) {

      Animal.call(this, name, color);

   }

image.png

组合继承

实现

原理:组合原型链继承和借用构造函数继承,用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

解决了原型链继承中父类引用类型的属性被所有子类实例共享问题以及借用构造函数继承中只能继承父类的实例属性和方法却不能继承原型属性/方法的问题,使子类实例共享引用对象子类实例既不共享父类的引用类型的数据,也继承了原型。

如何解决父类引用类型的属性被所有子类实例共享问题?

因为构造函数会将属性附加到子类实例上,访问属性的时候直接会访问子类实例上的属性,相当于子类实例上的属性直接屏蔽了原型上的属性,避免了共享个问题的出现

复制代码

  function Pig(name, color) {

      Animal.call(this, name, color);

  }

  Pig.prototype = new Animal();

  Pig.prototype.constructor = Pig;

  let firstPig = new Pig('猪', '白');

复制代码

缺陷

由于调用了两次Animal,会导致有重复属性

console.log(firstPig)

image.png

每个子类都有父类实例函数的副本,影响性能

图解

image.png

原型式继承

实现

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

实现1:

复制代码

  let cattle = {

      name:'牛',

      attribute: {

          color: '黄',

      }

  }

  let firstCattle = Object.create(cattle);

复制代码

实现2:

复制代码

  function object(obj){

      function F(){};

      F.prototype = obj;

      return new F();

  }

  let cattle = {

      name:'牛',

      attribute: {

          color: '黄',

      }

  }

  let firstCattle = object(cattle);

复制代码

缺陷

引用类型的属性被实例共享

复制代码

    let secondCattle = object(cattle);

    console.log(firstCattle.attribute); // 黄

    console.log(secondCattle.attribute); // 黄

    firstCattle.attribute.color = '红';

    console.log(firstCattle.attribute); // 红

    console.log(secondCattle.attribute); // 红

复制代码

子类实例化时无法传参

图解

image.png

寄生继承

实现

在原型式继承的基础上,增强对象,返回构造函数。

复制代码

  let sheep = {

      name: '羊',

      action: (currrentAction)=>{

          console.log(currrentAction)

      }

  }

  function createSheep(params) {

      let clone = object(params);// 此处的object就是上文中原型式继承的object方法

      clone.say = ()=>{

          console.log('咩咩咩');

      }

      return clone;

  }

  let anSheep = createSheep(sheep);

复制代码

缺陷

引用类型的属性被实例共享(可参考原型式继承)

子类实例化时无法传参

图解

image.png

寄生组合式继承

实现

结合借用构造函数传递参数和寄生模式实现继承。

只调用了一次Animal构造函数,因此避免了在Chicken.prototype 上创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()。这是最成熟的方法,也是现在库实现的方法

复制代码

  function Chicken(name, color){

      // 借用构造函数传递增强子类实例属性(支持传参和避免篡改)

      Animal.call(this, name);

  }

  // 将父类原型指向子类

    let clonePrototype = Object.create(Animal.prototype); // 创建对象,创建父类原型的一个副

    clonePrototype.constructor = Chicken;// 增强对象,弥补因重写原型而失去的默认的constructor

    Chicken.prototype = clonePrototype; // 将新创建的对象赋值给子类的原型

  let firstChicken = new Chicken("鸡", "乌");

复制代码

缺陷

每个子类都有父类实例函数的副本,影响性能

图解

image.png

原型拷贝和构造函数实现继承

实现

结合借用构造函数传递参数和遍历父类的原型链循环赋值给子类原型链来实现继承。和组合继承以及寄生组合式继承一样会调用Amimal.call(),不同对是三者对原型链的处理方式不同

复制代码

  function Fish(name, color){

      Animal.call(this, name, color)

  }

  for(var key in Animal.prototype) {

      Fish.prototype[key] = Animal.prototype[key]

  }

  Fish.prototype.constructor = Fish;

  let firstFish = new Fish('鱼', '红');

复制代码

缺陷

不可遍历的属性不会被继承

图解

image.png

Class继承

实现

ES6提供的继承方式,其extends的实现和上述的寄生组合式继承方式一样.

复制代码

  class Rabbit {

      constructor(name) {

          this.name = name;

      }

      action(currentAction){

          console.log(`当前动作${currentAction}`)

      }

  }

  class FirstRabbit extends Rabbit{

      constructor(name){

          super('兔子');

      }

      ownName(){

      }

  }

  let firstRabbit = new FirstRabbit('小白兔')

  console.log(firstRabbit)

复制代码

我们来看下结果

image.png

我们可以看到class继承也是通过原型链实现的,实际上ES6的class只是一个语法糖。

混入方式继承多个对象

实现

通过借用构造函数继承和Object.assign()实现多继承。在寄生组合的基础上再进一步。

复制代码

    // 混入方式实现多继承

    function OthenClass(){}

    function Tiger(){

        Animal.call(this);

        OthenClass.call(this);

    }

    // 继承一个类

    Tiger.prototype = Object.create(Animal.prototype);

    // 混合其它

    Object.assign(Animal.prototype, OthenClass.prototype);

    // 重新指定constructor

    MyClass.prototype.constructor = MyClass;

复制代码

网站网页优化www.zg886.cn

你可能感兴趣的:(简单易懂的JS继承图解)