javascript 对象--继承

哇哦~今天迎来的是掘金后台编写文章的新页面,不知道这个页面是须要经过写文章才能打开的,仍是新上的啊哈哈啊哈javascript

本篇由来

其实本身平时在总结上是很是注重的,可是通常是总结的比较随意,会存放在本身的笔记软件中,推荐使用“语雀”,划分目录和页面的美观交互深得我心,固然,适合本身的一个笔记软件才是最重要的;接下来的半个月内,我也将从新回顾本身总结的知识点和项目中遇到的困难点,并整理到掘金文章中html

切入主题 对象

谈起对象,实际上是一个很需的概念,在大学课程java学习中就留下了一个很是清晰的词语“面向对象”编程,对于对象而言,不只仅是去建立、去继承,与之相呼应的还有一些理论性的名词,这些名词在javascript中多是不全体现的,更多体如今java语言程序中;java

理论名词介绍

我忽然想起大学考试卷子填写概念的场景了 🤯🤯🤯typescript

对象

       所谓对象,本质上就是指事物(包括人和物)在程序设计语言中的表现形式。这里的事物能够是任何的东西(如客观存在的对象,或者较为抽象的概念);
       好比 小狗,咱们将其理解成对象,它是具备某些明确的特征的(体重、颜色、名字等),除此以外,还可以执行某些动做(汪汪叫、吃饭、睡觉等),在javascript语言中这些明确的某些特征就是属性,可以执行的行为就是方法编程

        类就是具有某些共同特征的实体的集合,它是一种抽象的数据类型,它是对所具备相同特征实体的抽象。在面向对象的程序设计语言中,类是对一类“事物”的属性与行为的抽象。
好比:“老人”、“儿童”、“青年“、“中年人“是关于人的年龄层段的划分,他们的共有的某些特征,其实能够抽成“人”,segmentfault

image.png

图1: 类间关系

继承

    在传统的OOP环境中,继承一般指的是类与类之间的关系,但因为javascript中不存在类,所以它的继承只能发生在对象之间;
    当一个对象继承自另外一个对象时,一般会往其中加入新的方法,以扩展被继承的老对象。一般将这一过程称之为“B继承自A”或“B扩展自A”。另外对于新对象来讲,它能够根据本身的须要,从继承的那组方法中选择几个来从新定义。这样作并不会改变对象的接口,由于其方法名是相同的,只不过当调用新对象时,该方法的行为与以前不一样了markdown

封装

封装主要用于阐述对象中所包含的内容。封装概念一般由两部分组成:app

  • 相关的数据(用于存储属性)
  • 基于这些数据所能作的事(所能调用的方法)

    封装的目的是将信息隐藏,即方法与属性的可见性。通常而言,封装包括封装数据和封装实现     在许多语言的对象系统中,封装数据是由语法解析来实现的,这些语言提供了public、private、protected这些关键字来限定方法和属性的可见性,这种限定分类定义了对象用户所能访问的层次; 但javascript并无提供对这些关键字的支持,只能依赖变量的做用域来实现封装特性, 并且只能模拟出 public 和 private 这两种封装性。除了ECMAScript6中提供的let以外,通常经过函数来建立做用域:不过typescript就另当别论了;ide

多态

多态的实际含义是:同一个操做做用于不一样的对象上面,能够产生不一样的解释和不一样的执行结果。换句话说,给不一样的对象发送同一个消息的时候,这些对象会根据这个信息分别给出不一样的反馈; 本身停留理解不够深入,就不班门弄斧了,直接推荐连接阅读函数

理论总结

  • 对象:marry是一个学生(学生是一个对象)
  • 属性:marry是女生,黑头发、高个子
  • 方法:marry能学习、吃饭、睡觉
  • 类:marry是Person类的一个实例
  • 原型对象:marry是一个由Person对象扩展而来的新对象
  • 封装:marry对象包含了数据和基于这些数据的方法
  • 继承:marry,xiaoli,xiaotong都是分别扩展自Person对象的新对象
  • 多态:能够随时调用:marry,xiaoli,xiaotong这三个对象各自的talk方法,它们均可以正常工做,尽管这些方法会产生不一样的结果。marry注重页面设计,xiaoli注重页面实现,xiaotong注重页面测试

javascript继承实现

javascript的继承方式也有不少种,平常经常使用的方法也是很是多,而且不少书籍中,也有介绍继承的方法,本文参考《javascript 高级程序设计(第三版)》

原型链继承

在咱们了解的JavaScript中,对于原型链的使用是很是多的,被建立的对象能够经过原型链访问上级的对象,而原型链继承的方式正式运用了此访问关系;

原型链继承实现

function Super() {
      this.value = true;
    }
    Super.prototype.getValue = function () {
      return this.value;
    }
    function Sub() { }
    console.log("super", new Super())
    //Sub继承super
    Sub.prototype = new Super();
    //将sub的构造函数指向 Sub
    Sub.prototype.constructor = Sub;
    //建立实例
    var instance = new Sub();
    console.log('sub', instance)

    console.log(instance.getValue())
复制代码
图2 原型链继承的关系

subSuper以及instance之间关系是:

图3 sub和Super继承的链式关系

原型链继承存在问题

原型链最主要的问题在于包含引用类型值的原型属性会被全部实例共享,而这也正是为何要在构造函数中,而不是在原型对象中定义属性的缘由。 在经过原型来实现继承时,原型实际上会变成另外一个类型的实例。因而,原先的实例属性也就瓜熟蒂落地变成了如今的原型属性了;
构造函数包含引用类型

function Super1() {
      this.colors = ['red', 'blue', 'green'];
    }
    function Sub1() { };
    //Sub继承了Super
    Sub1.prototype = new Super1();
    var instance1 = new Sub1();
    //查看instance1中的属性
    console.log('instance1',instance1)
    instance1.colors.push('black');
    console.log('instance1.colors',instance1.colors);//'red,blue,green,black'
    var instance2 = new Sub1();
    console.log('instance2 经过Sub1构造出来的',instance2.colors);//'red,blue,green,black'
复制代码
图4 上级构造函数包含引用类型会被修改
  • 原型链继承方式,对于复杂数据类型是引用关系,不是每一个构造函数中单独享有;
  • 建立子类型的是互殴,不能向超类型的构造函数中传递参数,在不少的状况下,实际上是须要传递参数场景的

原型继承

借助原型能够基于已有的对象来建立新对象,同时没必要所以建立自定义类型;
具体的实现方式;

原型继承实现

function object(o) {
      function F() {   };
      F.prototype = o;
      return new F();
    }
    var superObj = {
      init: function (value) {
        this.value = value;
      },
      getValue: function () {
        return this.value;
      }
    }

    var subObj = object(superObj);
    console.log('subObj',subObj)
    subObj.init('sub');
    console.log('subObj.getValue',subObj.getValue());//'sub'
复制代码

与原型链继承的关系;它们的一个重要区别是父类型的实例对象再也不做为子类型的原型对象; 这种方式更相似于Object.create()的实现

function Super(){
        this.value = 1;
    }
    Super.prototype.value = 0;
    function Sub(){};

    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;

    //建立子类型的实例对象
    var instance = new Sub;
    console.log(instance.value);//0
复制代码
function F(){};
F.prototype = Super.prototype;
Sub.prototype = new F();
复制代码

由上面代码看出,子类的原型对象是临时类F的实例对象,而临时类F的原型对象又指向父类的原型对象;因此,实际上,子类能够继承父类的原型上的属性,但不能够继承父类的实例上的属性; 原型继承和原型链继承都是共享父例引用类型的值;

查看引用类型引用问题

function object(o) {
      function F() { };
      F.prototype = o;
      return new F();
    }
    var superObj = {
      colors: ['red', 'blue', 'green']
    };
    var subObj1 = object(superObj);
    subObj1.colors.push("black");

    var subObj2 = object(superObj);
    subObj2.colors.push("white");

    console.log('superObj.colors',superObj.colors);
    console.log('subObj1.colors',subObj1.colors);
复制代码
图5 原型继承引用问题
😂😂 引用的问题 爱咱们如初

借用构造函数

借用构造函数,很是深入明了了,实际上是借助super的属性,即在子类型构造函数的内部调用超类型构造函数,经过使用apply()和call()方法在新建立的对象上执行构造函数;

借用构造继承实现

//借用构造函数
    function Super2() {
      console.log("Super2执行了")
      this.colors = ['red', 'blue', 'green'];
    } 
    function Sub2() {
      //继承了Super
      Super2.call(this);
    }
    var instance2 = new Sub2();
    instance2.colors.push('black');
    console.log('instance2.colors',instance2.colors);// ['red','blue','green','black']
    var instance3 = new Sub2();
    console.log('instance3.colors',instance3.colors);// ['red','blue','green']
复制代码
图6 借用构造函数执行

此时咱们的引用类型也不会存在冲突啦,这个主要的缘由仍是由于当咱们Sub2中执行的时候,Super2的当前执行做用域在Sub2中,造成了内部的变量关系;

借用构造函数的缺点

  • Super2中的原型上的方法是没法继续使用的
  • 不符合咱们继承的实现目的

寄生组合式继承

借用构造函数可以帮助咱们解决对象引用问题,原型链继承可以帮助咱们解决调用原型方法问题,若是将这两种方式组合在一块儿;岂不美哉~ 原型链继承+借用构造函数继承=既能够获取构造函数中的方法,也可以断引用;
组合继承(combination inheritance)有时也叫伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥两者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而经过借用构造函数来实现对实例属性的继承。这样,既经过在原型上定义方法实现了函数复用,又可以保证每一个实例都有它本身的属性;

寄生组合式继承实现

var count = 0; //用于计数
    function Super(name) {
      console.log('Super==count',count++)
      this.name = name;
      this.colors = ['red', 'blue', 'green'];
    }
    Super.prototype.sayName = function () {
      console.log('Super==this.name',this.name);
    };

    function Sub(name, age) {
      console.log('Sub==count',count++) 
      // 第二次调用Super(),Sub.prototype又获得了name和colors两个属性,并对上次获得的属性值进行了覆盖
      Super.call(this, name);
      this.age = age;
    }
    //继承方法
    // 第一次调用Super(),Sub.prototype获得了name和colors两个属性
    Sub.prototype = new Super();
    Sub.prototype.constructor = Sub;

    Sub.prototype.sayAge = function () {
      console.log('Sub===this.age',this.age);
    }
    
    var instance1 = new Sub("bai", 29);
    instance1.colors.push("black");
    console.log('instance1.colors',instance1.colors);//['red','blue','green','black']
    instance1.sayName();//"bai"
    instance1.sayAge();//29
    console.log('Super',new Super())
复制代码
图7 寄生组合式 关系

寄生组合式优缺点

  • 优势
    • 定义在Super中引用类型数据独立
    • 可以向构造函数Super中传入自定义的参数
  • 缺点
    • Super调用次数问题,继承时候调用一次,实例化Sub后再次调用了Super
    • 每次实例化Sub的时候都会调用Super.call

寄生式继承

寄生组合式继承带来的影响须要调用两次Super。寄生组合式继承与组合继承类似,都是经过借用构造函数来继承不可共享的属性,经过原型链的混成形式来继承方法和可共享的属性。只不过把原型继承的形式变成了寄生式继承。使用寄生组合式继承能够没必要为了指定子类型的原型而调用父类型的构造函数
寄生式继承只继承了父类型的原型属性,而父类型的实例属性是经过借用构造函数的方式来获得的;也就是 咱们借用Super的构造函数,可是去复制Super的原型函数;

寄生式继承实现

function Super(name) {
      console.log("Super === 执行")
      this.name = name;
      this.colors = ["red", "blue", "green"];
    }
    Super.prototype.sayName = function () {
      console.log("this.name",this.name)
      return this.name;
    };

    function Sub(name, age) {
      //第一次 借用构造函数
      Super.call(this, name);
      this.age = age;
    }
    
    //继承的方法 就是拷贝原型
    if (!Object.create) {
      Object.create = function (proto) {
        function F() { };
        F.prototype = proto;
        return new F();
      }
    }
    // 利用复制原型的方式进行操做 将Super.prototype的原型方法经过实例化给Sub
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;

    var instance1 = new Sub("bai", 29);
    instance1.colors.push("black");
    console.log('instance1.colors',instance1.colors);//['red','blue','green','black']
    instance1.sayName();//"bai"

    var instance2 = new Super("hu", 27);
    console.log("instance2.colors",instance2.colors);//['red','blue','green']
    instance2.sayName();//"hu"
复制代码
图8 寄生式原型继承

这三者的链式关系

图9 寄生原型继承
这三者的关系其实很难画出来😞😞 不过可以知道的是,每次实例化```Sub```时候都是造成的独立做用域,两个对象互不影响;

参考文档

ps:很久以前整理的,查过不少文档博客内容,可是没有一一记录下来,我仍是很尊重知识来源的的🥳🥳

相关文章
相关标签/搜索