关于前端:JavaScript-中的这些继承方式你弄懂了吗

2次阅读

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

面试中咱们常常会被问到继承,心愿通过此文,你能彻底搞懂 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); // Twittytop
console.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); // Twittytop
console.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); // Twittytop
console.log(ins.getName()); // Twittytop

寄生式组合继承汲取了盗用构造函数和寄生式继承的长处,又没有组合继承中调用父类构造函数两次的有余,是 ES5 实现继承的最佳模式。

对于 ES6 的继承,这里就不介绍了,它实质是上述继承的语法糖而已。

写在前面

JavaScript 继承独特的中央就是它的原型,如果这篇文章能让你对 JavaScript 继承有进一步的理解,那将是我最大的快慰。如果你感觉能学到一点货色的话,还请动动你可恶的小指让更多人看到。如果有谬误或者有疑难的中央,也欢送交换探讨。

正文完
 0