原型链继承

原型链继承是比拟常见的继承形式之一,其中波及的构造函数、原型和实例,三者之间存在着肯定的关系,即每一个构造函数都有一个原型对象,原型对象又蕴含一个指向构造函数的指针,而实例则蕴含一个原型对象的指针。例如:

function Parent1() {    this.name = 'parent1';    this.play = [1, 2, 3]}function Child1() {    this.type = 'child2';}Child1.prototype = new Parent1();console.log(new Child1());

下面的代码其实有一个潜在的问题,例如:

var s1 = new Child1();var s2 = new Child1();s1.play.push(4);console.log(s1.play);console.log(s2.play);

执行后果如下:

当我批改了s1的play属性的时候,s2的play属性也跟着变了,因为两个实例应用的是同一个原型对象。它们的内存空间是共享的,当一个发生变化的时候,另外一个也随之进行了变动,这就是应用原型链继承形式的一个毛病。

构造函数继承(借助 call)

function Parent1(){    this.name = 'parent1';}Parent1.prototype.getName = function () {    return this.name;}function Child1(){    Parent1.call(this);    this.type = 'child1'}let child = new Child1();console.log(child);  // 没问题console.log(child.getName());  // 会报错

运行后果如下:

除了 Child1 的属性 type 之外,也继承了 Parent1 的属性 name。这样写的时候子类尽管可能拿到父类的属性值,解决了第一种继承形式的弊病,但问题是,父类原型对象中一旦存在父类之前本人定义的办法,那么子类将无奈继承这些办法。

因而构造函数实现继承的优缺点,它使父类的援用属性不会被共享,优化了第一种继承形式的弊病;然而随之而来的毛病也比拟显著——只能继承父类的实例属性和办法,不能继承原型属性或者办法。

组合继承(前两种组合)

这种形式联合了前两种继承形式的优缺点,联合起来的继承,代码如下:

function Parent3 () {    this.name = 'parent3';    this.play = [1, 2, 3];  }  Parent3.prototype.getName = function () {    return this.name;  }  function Child3() {    // 第二次调用 Parent3()    Parent3.call(this);    this.type = 'child3';  }  // 第一次调用 Parent3()  Child3.prototype = new Parent3();  // 手动挂上结构器,指向本人的构造函数  Child3.prototype.constructor = Child3;  var s3 = new Child3();  var s4 = new Child3();  s3.play.push(4);  console.log(s3.play);  // 不相互影响  console.log(s4.play);  console.log(s3.getName()); // 失常输入'parent3'  console.log(s4.getName()); // 失常输入'parent3'

后果如下:

之前办法一和办法二的问题都得以解决,然而这里又减少了一个新问题:通过正文咱们能够看到 Parent3 执行了两次,第一次是扭转Child3 的 prototype 的时候,第二次是通过 call 办法调用 Parent3 的时候,那么 Parent3 多结构一次就多进行了一次性能开销。

原型式继承

ES5 外面的 Object.create 办法,这个办法接管两个参数:一是用作新对象原型的对象、二是为新对象定义额定属性的对象(可选参数)。

let parent4 = {    name: "parent4",    friends: ["p1", "p2", "p3"],    getName: function() {        return this.name;    }};let person4 = Object.create(parent4);person4.name = "tom";person4.friends.push("jerry");let person5 = Object.create(parent4);person5.friends.push("lucy");console.log(person4.name);console.log(person4.name === person4.getName());console.log(person5.name);console.log(person4.friends);console.log(person5.friends);

执行后果如下:

通过 Object.create 这个办法能够实现一般对象的继承,不仅仅能继承属性,同样也能够继承 getName 的办法。前三个输入都是失常的,最初两个输入后果统一是因为Object.create 办法是能够为一些对象实现浅拷贝的,那么对于这种继承形式的毛病也很显著,多个实例的援用类型属性指向雷同的内存。

寄生式继承

应用原型式继承能够取得一份指标对象的浅拷贝,而后利用这个浅拷贝的能力再进行加强,增加一些办法,这样的继承形式就叫作寄生式继承。

尽管其优缺点和原型式继承一样,然而对于一般对象的继承形式来说,寄生式继承相比于原型式继承,还是在父类根底上增加了更多的办法。实现如下:

let parent5 = {    name: "parent5",    friends: ["p1", "p2", "p3"],    getName: function() {        return this.name;    }};function clone(original) {    let clone = Object.create(original);    clone.getFriends = function() {        return this.friends;    };    return clone;}let person5 = clone(parent5);console.log(person5.getName());console.log(person5.getFriends());

输入后果如下:

从最初的输入后果中能够看到,person5 通过 clone 的办法,减少了 getFriends 的办法,从而使 person5 这个一般对象在继承过程中又减少了一个办法,这样的继承形式就是寄生式继承。

寄生组合式继承

联合第四种中提及的继承形式,解决一般对象的继承问题的 Object.create 办法,咱们在后面这几种继承形式的优缺点根底上进行革新,得出了寄生组合式的继承形式,这也是所有继承形式外面绝对最优的继承形式,代码如下:

function clone (parent, child) {    // 这里改用 Object.create 就能够缩小组合继承中多进行一次结构的过程    child.prototype = Object.create(parent.prototype);    child.prototype.constructor = child;}function Parent6() {    this.name = 'parent6';    this.play = [1, 2, 3];}Parent6.prototype.getName = function () {    return this.name;}function Child6() {    Parent6.call(this);    this.friends = 'child5';}clone(Parent6, Child6);    Child6.prototype.getFriends = function () {    return this.friends;}let person6 = new Child6();console.log(person6);console.log(person6.getName());console.log(person6.getFriends());

执行后果如下:

这种寄生组合式继承形式,根本能够解决前几种继承形式的毛病,较好地实现了继承想要的后果,同时也缩小了结构次数,缩小了性能的开销。整体看下来,这六种继承形式中,寄生组合式继承是这六种外面最优的继承形式。

ES6的extends关键字实现逻辑

ES6提供了extends语法糖,应用关键字很容易实现JavaScript的继承,先看一下extends应用办法。

class Person {  constructor(name) {    this.name = name  }  // 原型办法  // 即 Person.prototype.getName = function() { }  // 上面能够简写为 getName() {...}  getName = function () {    console.log('Person:', this.name)  }}class Gamer extends Person {  constructor(name, age) {    // 子类中存在构造函数,则须要在应用“this”之前首先调用 super()。    super(name)    this.age = age  }}const asuna = new Gamer('Asuna', 20)asuna.getName() // 胜利拜访到父类的办法

应用babel将ES6 的代码编译成 ES5,代码如下:

function _possibleConstructorReturn (self, call) {         // ...        return call && (typeof call === 'object' || typeof call === 'function') ? call : self; }function _inherits (subClass, superClass) {     // 这里能够看到    subClass.prototype = Object.create(superClass && superClass.prototype, {         constructor: {             value: subClass,             enumerable: false,             writable: true,             configurable: true         }     });     if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }var Parent = function Parent () {    // 验证是否是 Parent 结构进去的 this    _classCallCheck(this, Parent);};var Child = (function (_Parent) {    _inherits(Child, _Parent);    function Child () {        _classCallCheck(this, Child);        return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));}    return Child;}(Parent));

从下面编译实现的源码中能够看到,它采纳的也是寄生组合继承形式,因而也证实了这种形式是较优的解决继承的形式。