共计 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));
从下面编译实现的源码中能够看到,它采纳的也是寄生组合继承形式,因而也证实了这种形式是较优的解决继承的形式。