面试中咱们常常会被问到继承,心愿通过此文,你能彻底搞懂 JavaScript 中的继承原理。
前言
ES6 以前,JavaScript 中的继承不像其它 oo 语言一样,用特定 class 去实现,它是由构造函数和原型去模仿,上面咱们会介绍几种常见的继承办法以及对应的长处和有余。
原型链
什么是原型链?
比方我有一个构造函数,这个构造函数的实例有一个外部指针[[Prototype]]指向构造函数的原型,而后这个构造函数的原型又是另一个构造函数的实例,也就是说这个构造函数原型有一个外部指针[[Prototype]]指向另一个构造函数的原型,如此上来,就形成了一条原型链。那用原型链实现继承用代码示意进去就是这样:
function Parent () { this.name = 'Twittytop';}Parent.prototype.getName = function () { return this.name;}function Child () { this.age = 29;}// 继承Child.prototype = new Parent();var ins = new Child();console.log(ins.getName()); // Twittytop
这样原来在 Parent 上的属性都变成了 Child.prototype 上的属性。
问题
第一:共享问题
当 Parent 上蕴含有援用属性时,就出呈现问题,比方:
function Parent () { this.friends = ['Jack', 'Tom'];}function Child () { this.age = 29;}// 继承Child.prototype = new Parent();var ins1 = new Child();ins1.friends.push('Bob');var ins2 = new Child();console.log(ins2.friends); // ["Jack", "Tom", "Bob"]
因为继承之后变成了 Child 的原型属性,所以所有 Child 的实例都指向的是同一个 friends,当其中一个实例批改了这个值之后,变动就会反映到所有实例上。
第二: 传参问题
Child 在实例化是没法向 Parent 传参,当 Parent 依赖内部传参时,就会导致问题。
盗用构造函数
function Parent (name) { this.name = name;}Parent.prototype.getName = function () { return this.name;}function Child (name, age) { // 继承 Parent.call(this, name); this.age = age;}var ins = new Child('Twittytop', 29);console.log(ins.name); // Twittytopconsole.log(ins.getName); // undefined
能够看到,盗用构造函数的长处是能传递参数,问题是它只能继承实例属性,不能继承原型属性。
组合继承
既然原型链和盗用构造函数继承都有各自的毛病,那咱们能不能把这两者联合起来呢?这就是组合继承。
function Parent (name) { this.name = name;}Parent.prototype.getName = function () { return this.name;}function Child (name, age) { // 继承实例属性 Parent.call(this, name); this.age = age;}// 继承原型属性Child.prototype = new Parent();var ins = new Child('Twittytop', 29);console.log(ins.name); // Twittytopconsole.log(ins.getName()); // Twittytop
组合继承补救了原型链和盗用构造函数的有余,能同时继承实例属性和原型属性,但它的毛病是会调用两次父类构造函数。一次是在 Child 构造函数中执行 Parent.call,一次是在实例化 Parent 时。这样就会导致 Child 的不仅本身实例上有 name 属性,原型上也有 name 属性,导致了不必要的多余继承。用图示意如下:
原型式继承
function Parent (name) { this.name = 'Twittytop';}Parent.prototype.getName = function () { return this.name;}function Child (age) { this.age = age;}// 继承原型属性Child.prototype = Object.create(Parent.prototype);var ins = new Child(29);console.log(ins.getName);
原型式继承只继承了原型上的属性,没有继承实例属性,相比原型链继承更洁净,它没有把父类的实例属性继承到本身的原型下面,当然,它和原型链一样,也会有援用属性的共享问题。
寄生式继承
寄生式继承是建设在原型式继承根底上的,寄生式继承用代码表达出来是这样:
function inherit (Parent) { let pro = Object.create(Parent.prototype); pro.myMethod = function () {}; return pro;}
它相比原型式继承多了增加一些本人的属性和办法。
寄生式组合继承
寄生式组合继承综合了盗用构造函数和寄生式继承,它应用盗用构造函数继承实例属性,应用寄生式继承继承原型属性。
function inherit (Child, Parent) { let pro = Object.create(Parent.prototype); pro.constructor = Child; // 将constructor从新指回Child Child.prototype = pro;}function Parent (name) { this.name = name;}Parent.prototype.getName = function () { return this.name;}function Child (name, age) { // 继承实例属性 Parent.call(this, name); this.age = age;}// 继承原型属性inherit(Child, Parent)var ins = new Child('Twittytop', 29);console.log(ins.name); // Twittytopconsole.log(ins.getName()); // Twittytop
寄生式组合继承汲取了盗用构造函数和寄生式继承的长处,又没有组合继承中调用父类构造函数两次的有余,是ES5 实现继承的最佳模式。
对于 ES6 的继承,这里就不介绍了,它实质是上述继承的语法糖而已。
写在前面
JavaScript 继承独特的中央就是它的原型,如果这篇文章能让你对 JavaScript 继承有进一步的理解,那将是我最大的快慰。如果你感觉能学到一点货色的话,还请动动你可恶的小指让更多人看到。如果有谬误或者有疑难的中央,也欢送交换探讨。