摘要:實(shí)例擁有構(gòu)造函數(shù)屬性,該屬性返回創(chuàng)建實(shí)例對(duì)象的構(gòu)造函數(shù)。在考慮對(duì)象而不是自定義類型和構(gòu)造函數(shù)的情況下,寄生式繼承也是一種有用的模式。在子類的構(gòu)造函數(shù)中,只有調(diào)用之后,才能使用關(guān)鍵字,否則報(bào)錯(cuò)。
不積跬步無以至千里。
關(guān)于【Step-By-Step】Step-By-Step (點(diǎn)擊進(jìn)入項(xiàng)目) 是我于 2019-05-20 開始的一個(gè)項(xiàng)目,每個(gè)工作日發(fā)布一道面試題。每個(gè)周末我會(huì)仔細(xì)閱讀大家的答案,整理最一份較優(yōu)答案出來,因本人水平有限,有誤的地方,大家及時(shí)指正。
如果想 加群 學(xué)習(xí),可以通過文末的公眾號(hào),添加我為好友。
更多優(yōu)質(zhì)文章可戳: https://github.com/YvetteLau/...
本周面試題一覽:
原型鏈繼承的基本思路是什么?有什么優(yōu)缺點(diǎn)?
借用構(gòu)造函數(shù)和組合繼承基本思路是什么?有什么優(yōu)缺點(diǎn)?
原型式繼承的基本思路是什么?有什么優(yōu)缺點(diǎn)?
寄生式繼承的基本思路是什么?有什么優(yōu)缺點(diǎn)?
寄生組合式繼承的基本思路是什么?有什么優(yōu)缺點(diǎn)?
本周是繼承專題,在開始之前,需要先了解構(gòu)造函數(shù)、原型和原型鏈的相關(guān)知識(shí)。
構(gòu)造函數(shù)構(gòu)造函數(shù)和普通函數(shù)的區(qū)別僅在于調(diào)用它們的方式不同,任何函數(shù),只要通過 new 操作符來調(diào)用,那它就可以作為構(gòu)造函數(shù);任何函數(shù),如果不通過 new 操作符來調(diào)用,那么它就是一個(gè)普通函數(shù)。
實(shí)例擁有 constructor(構(gòu)造函數(shù)) 屬性,該屬性返回創(chuàng)建實(shí)例對(duì)象的構(gòu)造函數(shù)。
function Person(name, age) { this.name = name; this.age = age; } var Yvette = new Person("劉小夕", 20); console.log(Yvette.constructor === Person); //true
有一點(diǎn)需要說明的是,除了基本數(shù)據(jù)類型的 constructor 外( null 和 undefined 無 constructor 屬性),constructor 屬性是可以被重寫的。因此檢測(cè)對(duì)象類型時(shí),instanceof 操作符比 contsrutor 更可靠一些。
function Person(name) { this.name = name; } function SuperType() { } var Yvette = new Person("劉小夕"); console.log(Yvette.constructor); //[Function: Person] Yvette.constructor = SuperType; console.log(Yvette.constructor); //[Function: SuperType]原型
我們創(chuàng)建的每個(gè)函數(shù)都有 prototype 屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。原型對(duì)象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法。
在默認(rèn)情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè) constructor 屬性,這個(gè)屬性包含一個(gè)指向 prototype 屬性所在函數(shù)的指針。
當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)指針,指向構(gòu)造函數(shù)的原型對(duì)象(可以通過實(shí)例的 __proto__ 來訪問構(gòu)造函數(shù)的原型對(duì)象)。
function Person(name) { this.name = name; } Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person("劉小夕"); var person2 = new Person("前端小姐姐"); //構(gòu)造函數(shù)原型對(duì)象上的方法和屬性被實(shí)例共享 person1.sayName(); person1.sayName();
實(shí)例.__proto__ === 構(gòu)造函數(shù).prototype
原型鏈簡(jiǎn)單回顧一下構(gòu)造函數(shù)、原型和實(shí)例的關(guān)系:
每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個(gè)可以執(zhí)行原型對(duì)象的內(nèi)部指針(可以通過 __proto 訪問)。
假如我們讓原型對(duì)象等于另一個(gè)類型的實(shí)例,那么此時(shí)原型對(duì)象包含一個(gè)指向另一個(gè)原型的指針,相應(yīng)地,另一個(gè)原型中也包含著一個(gè)指向另一個(gè)構(gòu)造函數(shù)的指針。加入另一個(gè)原型又是另一個(gè)類型的實(shí)例,那么上述關(guān)系仍然成立,如此層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條,這就是原型鏈的基本概念。
function SuperType() { this.type = "animal"; } SuperType.prototype.getType = function() { console.log(this.type); } function SubType() { } SubType.prototype = new SuperType(); SubType.prototype.sayHello = function() { console.log("hello"); } function SimType(name) { this.name = name; } SimType.prototype = new SubType(); SimType.prototype.sayHi = function() { console.log("hi"); } var instance = new SimType("劉小夕"); instance.getType();
一圖勝萬言:
調(diào)用 instance.getType() 會(huì)調(diào)用以下的搜索步驟:
搜索 instance 實(shí)例
搜索 SimType.prototype
搜索 SubType.prototype
搜索 SuperType.prototype,找到了 getType 方法
在找不到屬性或方法的情況下,搜索過程總是要一環(huán)一環(huán)地前行到原型鏈的末端才會(huì)停下來。
所有引用類型都繼承了 Object,這個(gè)繼承也是通過原型鏈實(shí)現(xiàn)的。如果在 SuperType.prototype 還沒有找到 getType,就會(huì)到 Object.prototype中找(圖中少畫了一環(huán))。
25. 原型鏈繼承原型鏈繼承的基本思想是利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。
如 SubType.prototype = new SuperType();
function SuperType() { this.name = "Yvette"; this.colors = ["pink", "blue", "green"]; } SuperType.prototype.getName = function () { return this.name; } function SubType() { this.age = 22; } SubType.prototype = new SuperType(); SubType.prototype.getAge = function() { return this.age; } SubType.prototype.constructor = SubType; let instance1 = new SubType(); instance1.colors.push("yellow"); console.log(instance1.getName()); //"Yvette" console.log(instance1.colors);//[ "pink", "blue", "green", "yellow" ] let instance2 = new SubType(); console.log(instance2.colors);//[ "pink", "blue", "green", "yellow" ]
可以看出 colors 屬性會(huì)被所有的實(shí)例共享(instance1、instance2、...)。
缺點(diǎn):
通過原型來實(shí)現(xiàn)繼承時(shí),原型會(huì)變成另一個(gè)類型的實(shí)例,原先的實(shí)例屬性變成了現(xiàn)在的原型屬性,該原型的引用類型屬性會(huì)被所有的實(shí)例共享。
在創(chuàng)建子類型的實(shí)例時(shí),沒有辦法在不影響所有對(duì)象實(shí)例的情況下給超類型的構(gòu)造函數(shù)中傳遞參數(shù)。
26. 借用構(gòu)造函數(shù)借用構(gòu)造函數(shù)的技術(shù),其基本思想為:
在子類型的構(gòu)造函數(shù)中調(diào)用超類型構(gòu)造函數(shù)。
function SuperType(name) { this.name = name; this.colors = ["pink", "blue", "green"]; } function SubType(name) { SuperType.call(this, name); } let instance1 = new SubType("Yvette"); instance1.colors.push("yellow"); console.log(instance1.colors);//["pink", "blue", "green", yellow] let instance2 = new SubType("Jack"); console.log(instance2.colors); //["pink", "blue", "green"]
優(yōu)點(diǎn):
可以向超類傳遞參數(shù)
解決了原型中包含引用類型值被所有實(shí)例共享的問題
缺點(diǎn):
方法都在構(gòu)造函數(shù)中定義,函數(shù)復(fù)用無從談起,另外超類型原型中定義的方法對(duì)于子類型而言都是不可見的。
27. 組合繼承組合繼承指的是將原型鏈和借用構(gòu)造函數(shù)技術(shù)組合到一塊,從而發(fā)揮二者之長(zhǎng)的一種繼承模式。基本思路:
使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,通過借用構(gòu)造函數(shù)來實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承,既通過在原型上定義方法來實(shí)現(xiàn)了函數(shù)復(fù)用,又保證了每個(gè)實(shí)例都有自己的屬性。
function SuperType(name) { this.name = name; this.colors = ["pink", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); } function SuberType(name, age) { SuperType.call(this, name); this.age = age; } SuberType.prototype = new SuperType(); SuberType.prototype.constructor = SuberType; SuberType.prototype.sayAge = function () { console.log(this.age); } let instance1 = new SuberType("Yvette", 20); instance1.colors.push("yellow"); console.log(instance1.colors); //[ "pink", "blue", "green", "yellow" ] instance1.sayName(); //Yvette let instance2 = new SuberType("Jack", 22); console.log(instance2.colors); //[ "pink", "blue", "green" ] instance2.sayName();//Jack
缺點(diǎn):
無論什么情況下,都會(huì)調(diào)用兩次超類型構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時(shí)候,另一次是在子類型構(gòu)造函數(shù)內(nèi)部。
優(yōu)點(diǎn):
可以向超類傳遞參數(shù)
每個(gè)實(shí)例都有自己的屬性
實(shí)現(xiàn)了函數(shù)復(fù)用
28. 原型式繼承原型繼承的基本思想:
借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類型。
function object(o) { function F() { } F.prototype = o; return new F(); }
在 object() 函數(shù)內(nèi)部,先穿甲一個(gè)臨時(shí)性的構(gòu)造函數(shù),然后將傳入的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型,最后返回了這個(gè)臨時(shí)類型的一個(gè)新實(shí)例,從本質(zhì)上講,object() 對(duì)傳入的對(duì)象執(zhí)行了一次淺拷貝。
ECMAScript5通過新增 Object.create()方法規(guī)范了原型式繼承。這個(gè)方法接收兩個(gè)參數(shù):一個(gè)用作新對(duì)象原型的對(duì)象和(可選的)一個(gè)為新對(duì)象定義額外屬性的對(duì)象(可以覆蓋原型對(duì)象上的同名屬性),在傳入一個(gè)參數(shù)的情況下,Object.create() 和 object() 方法的行為相同。
var person = { name: "Yvette", hobbies: ["reading", "photography"] } var person1 = Object.create(person); person1.name = "Jack"; person1.hobbies.push("coding"); var person2 = Object.create(person); person2.name = "Echo"; person2.hobbies.push("running"); console.log(person.hobbies);//[ "reading", "photography", "coding", "running" ] console.log(person1.hobbies);//[ "reading", "photography", "coding", "running" ]
在沒有必要?jiǎng)?chuàng)建構(gòu)造函數(shù),僅讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持相似的情況下,原型式繼承是可以勝任的。
缺點(diǎn):
同原型鏈實(shí)現(xiàn)繼承一樣,包含引用類型值的屬性會(huì)被所有實(shí)例共享。
29. 寄生式繼承寄生式繼承是與原型式繼承緊密相關(guān)的一種思路。寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個(gè)僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部已某種方式來增強(qiáng)對(duì)象,最后再像真地是它做了所有工作一樣返回對(duì)象。
function createAnother(original) { var clone = object(original);//通過調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象 clone.sayHi = function () {//以某種方式增強(qiáng)這個(gè)對(duì)象 console.log("hi"); }; return clone;//返回這個(gè)對(duì)象 } var person = { name: "Yvette", hobbies: ["reading", "photography"] }; var person2 = createAnother(person); person2.sayHi(); //hi
基于 person 返回了一個(gè)新對(duì)象 -—— person2,新對(duì)象不僅具有 person 的所有屬性和方法,而且還有自己的 sayHi() 方法。在考慮對(duì)象而不是自定義類型和構(gòu)造函數(shù)的情況下,寄生式繼承也是一種有用的模式。
缺點(diǎn):
使用寄生式繼承來為對(duì)象添加函數(shù),會(huì)由于不能做到函數(shù)復(fù)用而效率低下。
同原型鏈實(shí)現(xiàn)繼承一樣,包含引用類型值的屬性會(huì)被所有實(shí)例共享。
30. 寄生組合式繼承所謂寄生組合式繼承,即通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混成形式來繼承方法,基本思路:
不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù),我們需要的僅是超類型原型的一個(gè)副本,本質(zhì)上就是使用寄生式繼承來繼承超類型的原型,然后再將結(jié)果指定給子類型的原型。寄生組合式繼承的基本模式如下所示:
function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); //創(chuàng)建對(duì)象 prototype.constructor = subType;//增強(qiáng)對(duì)象 subType.prototype = prototype;//指定對(duì)象 }
第一步:創(chuàng)建超類型原型的一個(gè)副本
第二步:為創(chuàng)建的副本添加 constructor 屬性
第三步:將新創(chuàng)建的對(duì)象賦值給子類型的原型
至此,我們就可以通過調(diào)用 inheritPrototype 來替換為子類型原型賦值的語句:
function SuperType(name) { this.name = name; this.colors = ["pink", "blue", "green"]; } //...code function SuberType(name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SuberType, SuperType); //...code
優(yōu)點(diǎn):
只調(diào)用了一次超類構(gòu)造函數(shù),效率更高。避免在SuberType.prototype上面創(chuàng)建不必要的、多余的屬性,與其同時(shí),原型鏈還能保持不變。
因此寄生組合繼承是引用類型最理性的繼承范式。
ES6 繼承Class 可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承,如:
class SuperType { constructor(age) { this.age = age; } getAge() { console.log(this.age); } } class SubType extends SuperType { constructor(age, name) { super(age); // 調(diào)用父類的constructor(x, y) this.name = name; } getName() { console.log(this.name); } } let instance = new SubType(22, "劉小夕"); instance.getAge(); //22
對(duì)于ES6的 class 需要做以下幾點(diǎn)說明:
類的數(shù)據(jù)類型就是函數(shù),類本身就指向構(gòu)造函數(shù)。
console.log(typeof SuperType);//function console.log(SuperType === SuperType.prototype.constructor); //true
類的內(nèi)部所有定義的方法,都是不可枚舉的。(ES5原型上的方法默認(rèn)是可枚舉的)
Object.keys(SuperType.prototype);
constructor 方法是類的默認(rèn)方法,通過 new 命令生成對(duì)象實(shí)例時(shí),自動(dòng)調(diào)用該方法。一個(gè)類必須有constructor 方法,如果沒有顯式定義,一個(gè)空的 constructor 方法會(huì)被默認(rèn)添加。
Class 不能像構(gòu)造函數(shù)那樣直接調(diào)用,會(huì)拋出錯(cuò)誤。
使用 extends 關(guān)鍵字實(shí)現(xiàn)繼承,有一點(diǎn)需要特別說明:
子類必須在 constructor 中調(diào)用 super 方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。如果沒有子類沒有定義 constructor 方法,那么這個(gè)方法會(huì)被默認(rèn)添加。在子類的構(gòu)造函數(shù)中,只有調(diào)用 super 之后,才能使用 this關(guān)鍵字,否則報(bào)錯(cuò)。這是因?yàn)樽宇悓?shí)例的構(gòu)建,基于父類實(shí)例,只有super方法才能調(diào)用父類實(shí)例。
class SubType extends SuperType { constructor(...args) { super(...args); } }參考文章:
[1] 珠峰架構(gòu)課(墻裂推薦)
[2] CSS-清除浮動(dòng)
[3] 詳解JS函數(shù)柯里化
[4] JavaScript數(shù)組去重
謝謝各位小伙伴愿意花費(fèi)寶貴的時(shí)間閱讀本文,如果本文給了您一點(diǎn)幫助或者是啟發(fā),請(qǐng)不要吝嗇你的贊和Star,您的肯定是我前進(jìn)的最大動(dòng)力。 https://github.com/YvetteLau/...
關(guān)注公眾號(hào),加入技術(shù)交流群。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/105248.html
摘要:關(guān)于點(diǎn)擊進(jìn)入項(xiàng)目是我于開始的一個(gè)項(xiàng)目,每個(gè)工作日發(fā)布一道面試題。那個(gè)率先改變的實(shí)例的返回值,就傳遞給的回調(diào)函數(shù)。通過插入標(biāo)簽的方式來實(shí)現(xiàn)跨域,參數(shù)只能通過傳入,僅能支持請(qǐng)求。因此清除浮動(dòng),只需要觸發(fā)一個(gè)即可。 關(guān)于【Step-By-Step】 Step-By-Step (點(diǎn)擊進(jìn)入項(xiàng)目) 是我于 2019-05-20 開始的一個(gè)項(xiàng)目,每個(gè)工作日發(fā)布一道面試題。每個(gè)周末我會(huì)仔細(xì)閱讀大家的...
摘要:關(guān)于點(diǎn)擊進(jìn)入項(xiàng)目是我于開始的一個(gè)項(xiàng)目,每個(gè)工作日發(fā)布一道面試題。的狀態(tài)由決定,分成以下兩種情況只有的狀態(tài)都變成,的狀態(tài)才會(huì)變成,此時(shí)的返回值組成一個(gè)數(shù)組,傳遞給的回調(diào)函數(shù)。 關(guān)于【Step-By-Step】 Step-By-Step (點(diǎn)擊進(jìn)入項(xiàng)目) 是我于 2019-05-20 開始的一個(gè)項(xiàng)目,每個(gè)工作日發(fā)布一道面試題。每個(gè)周末我會(huì)仔細(xì)閱讀大家的答案,整理最一份較優(yōu)答案出來,因本人...
閱讀 1938·2021-11-23 09:51
閱讀 1250·2019-08-30 15:55
閱讀 1623·2019-08-30 15:44
閱讀 768·2019-08-30 14:11
閱讀 1150·2019-08-30 14:10
閱讀 921·2019-08-30 13:52
閱讀 2636·2019-08-30 12:50
閱讀 621·2019-08-29 15:04