如何回答關於 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);

// 等等等。。。
複製代碼

因此,不要回答那麼多,可是必定要本身理解了這個過程,只要你知道最好的繼承方式是什麼,相信至少在這個問題上不會卡殼,而且這也是咱們平常開發中,能夠真正用起來的最佳實踐方案~

相關文章
相關標籤/搜索