如何回答关于 JS 的继承

序言

最近从某个大佬的github博客上看到一个关于js继承的博客,先放上来供你们参考:javascript

JavaScript深刻之继承的多种方式和优缺点java

看完以后,总结了几个点:git

为何说寄生组合式继承是最优的?

做者引用了高程的解释:github

引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:面试

这种方式的高效率体现它只调用了一次 Parent 构造函数,而且所以避免了在 Parent.prototype 上面建立没必要要的、多余的属性。与此同时,原型链还能保持不变;所以,还可以正常使用 instanceof 和 isPrototypeOf。开发人员广泛认为寄生组合式继承是引用类型最理想的继承范式。函数

固然,这个确定是没有问题,咱们在延伸一点东西出来:性能

这里为了方便,我把相关代码一块儿贴过来,供参考ui

涉及原型链继承的问题

代码以下:this

function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy", "yayu"]
复制代码

js的面向对象是基于原型和原型链的,不像java这种语言,java中的继承会真正生成一个与父类彻底无关的子类,子类new出来的实例是一个单独的实例,不论你new多少个都是隔离的,然而js并非这样的,熟悉js的小伙伴都知道,用原型链继承会致使一个很大的问题,就是“共享父类属性”的问题,全部的子类实例会共享一个属性,就比如你有苹果这个属性,可是你的苹果实际上并非你的,是你从父类那里继承过来的,并且,父类能够吃掉你的苹果,你的兄弟姐妹们也能够吃掉你的苹果,好吧,想一想就可怕。spa

使用单纯的调用父类构造函数继承的问题

代码(有改动):

function Parent () {
    this.names = ['kevin', 'daisy'];
    this.getNames = fucntion(){
        return this.names;
    }
}

function Child () {
    Parent.call(this);
}

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.getNames()); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.getNames()); // ["kevin", "daisy"]
复制代码

能够看到共享的问题解决了,可是,有一个额外的问题,咱们是基于原型链的,可是咱们并无真正的去利用原型链的共享功能,彻底抛弃了它,而且致使每次new 实例的时候,都会去调用父类的构造方法去加到子类的实例上,是彻底的copy paste过程,这等于舍弃了js原型链的精髓部分,这样的代码天然是没有灵魂的~

组合继承?

代码:

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {

    Parent.call(this, name);
    
    this.age = age;

}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child('kevin', '18');

child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('daisy', '20');

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
复制代码

new Child的过程当中会调用一次父类的构造方法,而且在指定Child的原型的时候,又会调用一次,这明显会形成一些问题,child实例上有一个本身的name,同时还有一个父类给的name,这明显不是最优的方案~

寄生组合

代码:

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

// 关键的三步
var F = function () {};

F.prototype = Parent.prototype;

Child.prototype = new F();


var child1 = new Child('kevin', '18');

console.log(child1);
复制代码

首先要理解什么是寄生,本来经过直接把父类的实例对象指给子类的原型的方式中,存在一个比较尴尬的问题,子类能够拿到父类以及父类原型的全部属性,同时组合模式下,子类上拥有的属性在父类也会有,所以咱们用一个中间类,这个函数是一个空函数,咱们就用它来把原型链串起来,可是不给他任何属性,这样子类的原型经过这个中间的F的实例,就能够直接访问到本来想要访问的父类属性了,同时new F() 的过程避免了重复调用本来父类的过程,比起new Parent(), new F()的性能会更好,反作用也更少,核心思想就是:

属性,好比name,colors 不须要共享的,经过调用父类构造函数

方法,好比getName,须要共享的能够直接设置到父类的原型对象上

那么,你觉得,这真的是最优的js继承方式吗?

到底有没有最优的继承方式?

从“对象”谈起

任何一个天然界的对象,大方面都会有2种属性,一种静态属性【属性】,一种动态属性【方法或者行为】,好比对于一个天然人,名字,年龄,性别,地址等等,都是静态属性,它们是不能够被共享的,还有动态属性,好比:说话,走路,吃东西,等等叫作动态属性,那么咱们在设计继承的时候,到底应该怎么划分?每一个人的静态属性隔离,动态属性共享,就够了吗?

好比:每个人都有鼻子,有眼睛,有头有脑,这些静态属性应该隔离吗?

再好比:羽毛球运动员都会打羽毛球,乒乓球运动员都会打乒乓球,这些动态属性都应该共享吗?

固然不是。

最优解

继承的最优解实际上是要看当前应用场景的,最符合预期的场景就是,须要共享的,不管是静态的仍是动态的,把它们放在parent的原型上,须要隔离的,把它们放在parent上,而后子类经过调用parent的构造方法来初始化为自身的属性,这样,才是真正的“最佳继承设计方式”。

总结:

当面试者问你的时候,我以为不必答什么寄生组合什么的【它只是个名字】,最优的继承方式:

对于当前须要被设计为共享属性的属性,所有经过设置原型的方式挂到父类的原型上,不分静态和动态,维度划分是是否能够共享,对于每一个子类都不同的属性,应该放到父类的构造方法上,而后经过子类调用父类构造方法的方式来实现初始化过程,对于子类独有的属性,咱们经过扩展子类构造方法的方式实现,那么对于每个子类如何拿到父类的原型方法,就须要将子类的构造方法的原型与父类构造方法的原型进行原型链关联的操做,看个具体的例子:

const Man = function(name, age) {
    // 须要都要可是值不同的属性,在父类的构造方法中定义
    this.name = name;
    this.age = age;
};

Man.prototype.say = function() {
    // 须要共享的动态属性
    console.log(`i am ${this.name}, i am ${this.age} years old`);
};

Man.prototype.isMan = true; // 须要共享的静态属性

const Swimmer = function(name, age, vitalCapacity) {
    Man.call(this, name, age); //继承“人类”
    this.vitalCapacity = vitalCapacity; // 对于游泳员来讲肺活量是很重要的一个指标,是每一个游泳员都须要可是值都不一样的属性
};

const BasketBaller = function(name, age, height) {
    Man.call(this, name, age); //继承“人类”
    this.height = height; // 对于篮球运动员来讲身高是一个都须要可是值都不一样的指标
};

// 咱们用es新的直接设置原型关系的方法来关联原型链
Object.setPrototypeOf(Swimmer.prototype, Man.prototype); // 设置子类原型和父类原型的原型链关系 达到共享原型上的属性的目的
Object.setPrototypeOf(BasketBaller.prototype, Man.prototype); // 同理

// 还能够继续扩展 Swimmer.prototype 或者 BasketBaller.prototype 上的公共属性哦

const swimmer1 = new Swimmer('swimmer1', 11, 100);
const swimmer2 = new Swimmer('swimmer2', 12, 200);

swimmer1.isMan // true 共享静态属性
swimmer1 // age: 11, name: "swimmer1", vitalCapacity: 100 自身属性有了
swimmer1.say() // i am swimmer1, i am 11 years old 共享动态属性

const basketBaller1 = new BasketBaller('basketBaller1', 20, 180);
const basketBaller2 = new BasketBaller('basketBaller2', 30, 187);

// 等等等。。。
复制代码

因此,不要回答那么多,可是必定要本身理解了这个过程,只要你知道最好的继承方式是什么,相信至少在这个问题上不会卡壳,而且这也是咱们平常开发中,能够真正用起来的最佳实践方案~

相关文章
相关标签/搜索