关于前端:JS-常见的-6-种继承方式

25次阅读

共计 4689 个字符,预计需要花费 12 分钟才能阅读完成。

原型链继承

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

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));

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

正文完
 0