原型链继承
原型链继承是比拟常见的继承形式之一,其中波及的构造函数、原型和实例,三者之间存在着肯定的关系,即每一个构造函数都有一个原型对象,原型对象又蕴含一个指向构造函数的指针,而实例则蕴含一个原型对象的指针。例如:
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));
从下面编译实现的源码中能够看到,它采纳的也是寄生组合继承形式,因而也证实了这种形式是较优的解决继承的形式。