JavaScript之深刻各类继承

1、继承的概念

继承,是面向对象语言的一个重要概念。
一般有这两种继承方式:接口继承实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。面试

《JS高程》里提到:“因为函数没有 签名,在‘ECMAScript’中没法实现 接口继承。”

等等,函数签名是什么东西?据MDN文档定义以下:segmentfault

函数签名(类型签名、方法签名)定义了函数或方法的输入与输出。数组

签名可包含如下内容:函数

  • 参数 及参数的 类型
  • 一个的返回值及其类型
  • 可能会抛出或传回的异常
  • 该方法在 面向对象程序中的可用性方面的信息(如public、static或prototype)。

看起来好复杂啊,咱们换成强类型的语言的角度会不会更好理解?
譬如,C的函数签名就是咱们熟悉的函数声明:this

int func(double d);

此时:参数名为d,参数类型为double,返回值为func(d),返回值类型为int
再如,Java的函数签名:prototype

public static void main(String[] args)

此时:参数名为args,参数类型为String [],返回值类型为void因此该方法没有返回值,访问修饰符public表示该方法是公有方法,static表示该方法是一个类方法而非实例方法……设计

如今,咱们知道函数签名是怎么回事了。那么,接口继承又是什么东西?相信你们会遥想起Java中的interfaceimplements等……在此就不班门弄斧了。code

咱们知道,JavaScript是类型松散的语言,不像Java它们有严格的变量类型检查。因此,JS的函数才没有签名,才没法实现接口继承。对象

那么,JS的实现继承是怎么回事呢?继承

2、JS的继承

  1. 原型链的基本模式
    理解:经过建立SuperType的实例,并将该实例赋给SubType.prototype,来实现SubType继承SuperTypeinstance的原型指向SubTypeSubType的原型指向SuperTypeSuperType的原型指向Object,如此构成了原型链。
    缺点:对象实例共享全部继承的属性和方法,不适宜单独适用

    function SuperType() {
        this.property = true;
    }
    SuperType.prototype.getSuperValue = function() {
        return this.property;
    };
    function SubType() {
      this.subproperty = false;
    }
    //SubType继承SuperType
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function() {
      return this.subproperty;
    };
    var instance = new SubType();
    alert(instance.getSuperValue());  //true

    因而,有下一个招式,来解决包含引用类型值的原型属性会被全部实例共享的弱点。

  2. 借用构造函数
    理解:“借用”超类型构造方法,在新的子类型对象上执行超类型函数定义的全部对象初始化代码
    适用:解决超类型的引用类型值被全部子类型对象实例共享,并且子类型可向超类型传参
    缺点:不能作到函数复用,从而下降效率,不适宜单独适用

    function SuperType(name) {
      this.name = name;
      this.colors = ["red", "blue", "green"]; //引用类型值
    }
    function SubType() {
      SuperType.call(this, "A"); //继承SuperType “借用”超类型的构造函数 并传参
      this.age = 20;    //实例属性
    }
    var instance1 = new SubType();    //instance1.name: "A", instance1.age: 20
    instance1.colors.push("black"); //instance1.colors: "red, blue, green, black"
    var instance2 = new SubType();  //instance2.colors: "red, blue, green"

    接下来,组合技能出大招!

  3. 组合继承
    理解:将原型链和借用构造函数组合到一块儿,经过原型链来继承共享的原型属性和方法,经过借用构造函数来继承实例属性。
    适用:最经常使用的继承模式。
    缺点:调用两次超类型构造函数,在SubType上建立了多余的属性,形成超类型对象的实例属性的重写

    function SuperType(name) {
      this.name = name;
      this.colors = ["red", "blue", "green"];
    }
    SuperType.prototype.sayName = function() {
      alert(this.name);
    };
    function SubType(name, age) {
      SuperType.call(this, name);   //继承属性 第二次调用超类型构造函数 新实例获得两个实例属性name,colors
      this.age = age;
    }
    SubType.prototype = new SuperType();  //继承方法 第一次调用超类型构造函数 SubType.prototype获得两个实例属性name,colors
    SubType.prototype.sayAge = function() {
      alert(this.age);
    };
    
    var instance1 = new SubType("妹妹", 18);
    instance1.colors.push("black");
    alert(instance1.colors);  //"red, blue, green, black"
    instance1.sayName();  //"妹妹"
    instance1.sayAge();  //18
    
    var instance2 = new SubType("弟弟", 20);
    alert(instance2.colors);  //"red, blue, green"
    instance2.sayName();  //"弟弟"
    instance2.sayAge();  //20

    这是打boss的大招,那我怎么对付小怪?

  4. 原型式继承
    理解:本质是对给定对象的浅复制
    适用:没必要预先定义构造函数来实现继承,只想让一个对象与另外一个对象保持相似
    缺点:引用类型值的属性被共享,如同原型模式同样

    function object(o) {    //对o进行浅复制
      function F() {}    //建立一个临时性的构造函数
      F.prototype = o;    //将传入的对象o做为F的原型
      return new F();    //返回F的新实例
    }
    
    var person = {
      name: "A",
      friends: ["B", "C", "D"]
    };
    var anotherPerson = object(person);    // Object.create(person)
    anotherPerson.name = "E";
    anotherPerson.friends.push("F");
    var yetAnotherPerson = object(person);    //Object.create(person)
    yetAnotherPerson.name = "G";
    yetAnotherPerson.friends.push("H");
    alert(person.friends);  //"B, C, D, F, H" 引用类型值的属性被共享啦

    注意:Object.create()方法的第二个参数,新对象的额外属性的对象,会覆盖原型对象上的同名属性。

    var anotherPerson = Object.create(person, {
      name: { value: "E" }
    });
    alert(anotherPerson.name);   // "E"

    打了这么久,能不能让小招也升级(封装)一下啊?

  5. 寄生式继承

    建立一个仅用于封装继承过程的函数,该函数在内部以某种方式来加强对象,最后再像真的是它作了全部工做同样返回对象。

    理解:继承的工做是经过调用函数实现的,因此是“寄生”,将继承工做寄托给别人作,本身只是作加强工做。
    适用:基于某个对象或某些信息来建立对象,而不考虑自定义类型和构造函数。
    缺点:不能作到函数复用,从而下降效率

    function createAnother(original) {
       var clone = object(original); //经过调用函数建立一个新对象
       clone.sayHi = function() {  //以某种方式来加强这个对象
         alert("hi");
       }
       return clone;
     }
     var person = {
       name: "A",
       friends: ["B", "C", "D"]
     };
     var anotherPerson = createAnother(person);  //不只有person全部属性方法,还有本身的sayHi方法
     anotherPerson.sayHi();  //"hi"

    经验攒足,我要把以前打boos的组合大招升到满级!

  6. 寄生组合继承
    理解:经过借用构造函数来继承属性,经过原型链的混用形式来继承方法。用寄生式继承来继承超类型的原型,再将加强后的结果指定给子类型的原型。
    适用:引用类型最理想的继承模式,高效,只调用了一个SuperType构造函数

    function inheritPrototype(subType, superType) {
      var prototype = object(superType.prototype); //建立对象 超类型原型的副本
      prototype.constructor = subType;  //加强对象 弥补因重写原型而失去默认的constructor属性
      subType.prototype = prototype;  //指定对象
    }
    
    function SuperType(name) {
      this.name = name;
      this.colors = ["red", "blue", "green"];
    }
    SuperType.prototype.sayName = function() {
      alert(this.name);
    };
    function SubType(name, age) {
      SuperType.call(this, name);   //继承属性
      this.age = age;
    }
    inheritPrototype(SubType, SuperType);  //继承方法
    SubType.prototype.sayAge = function() {
      alert(this.age);
    };

3、ES6的继承

extends关键字用来建立一个普通类或者内建对象的子类。
class A {
    ...
}
class B extends A {
    ...
}

其中,

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

由于extends实现了:

Object.setPrototypeOf(B.prototype, A.prototype);    //B.prototype.__proto__ = A.prototype;

Object.setPrototypeOf(B, A);    //B.__proto__ = A

extends更具体的实现方法参见面试官问:JS的继承,在此就不班门弄斧了~


完~如有不足,请多指教,不胜感激!

以上代码借鉴于《JS高级程序设计》
相关文章
相关标签/搜索