摘要:構(gòu)造函數(shù)所以,就有了畸形的繼承方式原型鏈繼承三原型鏈繼承改變構(gòu)造函數(shù)的原型對(duì)象繼承了屬性以上例子中,暴露出原型鏈繼承的兩個(gè)問題包含引用類型數(shù)據(jù)的原型屬性,會(huì)被所有實(shí)例共享,基本數(shù)據(jù)類型則不會(huì)。
前言
眾所周知,JavaScript 中,沒有 JAVA 等主流語言“類”的概念,更沒有“父子類繼承”的概念,而是通過原型對(duì)象和原型鏈的方式實(shí)現(xiàn)繼承。
于是,我們這一篇講一講 JS 中的繼承(委托)。
一、為什么要有繼承?JavaScript 是面向?qū)ο缶幊痰恼Z言,里面全是對(duì)象,而如果不通過繼承的機(jī)制將對(duì)象聯(lián)系起來,勢必會(huì)造成程序代碼的冗余,不方便書寫。
二、為什么又是原型鏈繼承?好,既然是 OO 語言,那么就加繼承屬性吧。但是 JS 創(chuàng)造者并不打算引用 class,不然 JS 就是一個(gè)完整的 OOP 語言了,而創(chuàng)造者 JS 更容易讓新手開發(fā)。
后來,JS 創(chuàng)造者就將 new 關(guān)鍵字創(chuàng)建對(duì)象后面不接 class,改成構(gòu)造函數(shù),又考慮到繼承,于是在構(gòu)造函數(shù)上加一個(gè)原型對(duì)象,最后讓所有通過 new 構(gòu)造函數(shù) 創(chuàng)建出來的對(duì)象,就繼承構(gòu)造函函數(shù)的原型對(duì)象的屬性。
function Person() { // 構(gòu)造函數(shù) this.name = "jay"; } Person.prototype = { sex: "male" } var person1 = new Person(); console.log(person1.name); // jay console.log(person1.sex); // male
所以,就有了 JavaScript 畸形的繼承方式:原型鏈繼承~
三、原型鏈繼承function Parent() { this.names = ["aa", "bb", "cc"]; this.age = 18; } function Child() { // ... } Child.prototype = new Parent(); // 改變構(gòu)造函數(shù)的原型對(duì)象 var child1 = new Child(); // 繼承了 names 屬性 console.log(child1.names); // ["aa", "bb", "cc"] console.log(child1.age); // 18 child1.names.push("dd"); child1.age = 20; var child2 = new Child(); console.log(child2.names); // ["aa", "bb", "cc", "dd"] console.log(child2.age); // 18
以上例子中,暴露出原型鏈繼承的兩個(gè)問題:
包含引用類型數(shù)據(jù)的原型屬性,會(huì)被所有實(shí)例共享,基本數(shù)據(jù)類型則不會(huì)。
在創(chuàng)建子類型實(shí)例時(shí),無法向父類型的構(gòu)造函數(shù)中傳遞參數(shù)。
四、call 或 apply 繼承function Parent(age) { this.names = ["aa", "bb", "cc"] this.age = age; } function Child() { Parent.call(this, 18); } var child1 = new Child(); // 繼承了 names 屬性 console.log(child1.names); // ["aa", "bb", "cc"] child1.names.push("dd"); console.log(child1.age); // 18 var child2 = new Child(); console.log(child2.names); // ["aa", "bb", "cc"] console.log(child2.age); // 18
call 或 apply 的原理是在子類型的構(gòu)造函數(shù)中,“借調(diào)”父類型的構(gòu)造函數(shù),最終實(shí)現(xiàn)子類型中擁有父類型中屬性的副本了。
call 或 apply 這種繼承方式在《JavaScript 高級(jí)程序設(shè)計(jì)》中叫作“借用構(gòu)造函數(shù)(constructor stealing)”,解決了原型鏈繼承中,引用數(shù)據(jù)類型被所有子實(shí)例共享的問題,也能夠?qū)崿F(xiàn)傳遞參數(shù)到構(gòu)造函數(shù)中,但唯一的問題在于業(yè)務(wù)代碼也寫在了構(gòu)造函數(shù)中,函數(shù)得不到復(fù)用。
五、組合繼承組合繼承(combination inheritance)也叫作偽經(jīng)典繼承,指的是,前面兩種方法:原型鏈繼承和 call 或 apply 繼承 組合起來,保證了實(shí)例都有自己的屬性,同時(shí)也能夠?qū)崿F(xiàn)函數(shù)復(fù)用:
function Parent(age) { this.names = ["aa", "bb", "cc"] this.age = age; } Parent.prototype.sayName = function () { console.log(this.names); } function Child() { Parent.call(this, 18); // 第一次調(diào)用 } Child.prototype = new Parent(); // 第二次調(diào)用:通過原型鏈繼承 sayName 方法 Child.prototype.constructor = Child; // 改變 constructor 為子類型構(gòu)造函數(shù) var child1 = new Child(); child1.sayName(); // ["aa", "bb", "cc"] child1.names.push("dd"); console.log(child1.age); // 18 var child2 = new Child(); console.log(child2.names); // ["aa", "bb", "cc"] console.log(child2.age); child2.sayName(); // ["aa", "bb", "cc"]
組合繼承將繼承分為兩步,一次是創(chuàng)建子類型關(guān)聯(lián)父類型原型對(duì)象的時(shí)候,另一次是在子類型構(gòu)造函數(shù)的內(nèi)部。是 JS 最常用的繼承方式。
六、原型式繼承原型式繼承說白了,就是將父類型作為一個(gè)對(duì)象,直接變成子類型的原型對(duì)象。
function object(o){ function F(){} F.prototype = o; return new F(); } var parent = { age: 18, names: ["aa", "bb", "cc"] }; var child1 = object(parent); // 繼承了 names 屬性 console.log(child1.names); // ["aa", "bb", "cc"] child1.names.push("dd"); console.log(child1.age); // 18 var child2 = object(parent); console.log(child2.names); // ["aa", "bb", "cc", "dd"] console.log(child2.age); // 18
原型式繼承其實(shí)就是對(duì)原型鏈繼承的一種封裝,它要求你有一個(gè)已有的對(duì)象作為基礎(chǔ),但是原型式繼承也有共享父類引用屬性,無法傳遞參數(shù)的缺點(diǎn)。
這個(gè)方法后來有了正式的 API: Object.create({...})
所以當(dāng)有一個(gè)對(duì)象,想讓子實(shí)例繼承的時(shí)候,可以直接用 Object.create() 方法。
七、寄生式繼承寄生式繼承是把原型式 + 工廠模式結(jié)合起來,目的是為了封裝創(chuàng)建的過程。
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 = { age: 18, names: ["aa", "bb", "cc"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); // "hi"八、 寄生組合式繼承
剛才說到組合繼承有一個(gè)會(huì)兩次調(diào)用父類的構(gòu)造函數(shù)造成浪費(fèi)的缺點(diǎn),寄生組合繼承就可以解決這個(gè)問題。
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); // 創(chuàng)建了父類原型的淺復(fù)制 prototype.constructor = subType; // 修正原型的構(gòu)造函數(shù) subType.prototype = prototype; // 將子類的原型替換為這個(gè)原型 } function SuperType(age){ this.age = age; this.names = ["aa", "bb", "cc"]; } SuperType.prototype.sayName = function(){ console.log(this.names); }; function SubType(age){ SuperType.call(this, age); this.age = age; } // 核心:因?yàn)槭菍?duì)父類原型的復(fù)制,所以不包含父類的構(gòu)造函數(shù),也就不會(huì)調(diào)用兩次父類的構(gòu)造函數(shù)造成浪費(fèi) inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function(){ console.log(this.age); } var child1 = new SubType(22) child1.sayAge() // 22 child1.sayName() // ["aa", "bb", "cc"]九、ES6 class extends
class Parent { constructor(name) { this.name = name; } doSomething() { console.log("parent do something!"); } sayName() { console.log("parent name:", this.name); } } class Child extends Parent { constructor(name, parentName) { super(parentName); this.name = name; } sayName() { console.log("child name:", this.name); } } const child = new Child("son", "father"); child.sayName(); // child name: son child.doSomething(); // parent do something! const parent = new Parent("father"); parent.sayName(); // parent name: father
ES6 的 class extends 本質(zhì)上是 ES5 的語法糖。
ES6實(shí)現(xiàn)繼承的具體原理:
class Parent { } class Child { } Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; } // B 的實(shí)例繼承 A 的實(shí)例 Object.setPrototypeOf(Child.prototype, parent.prototype); // B 繼承 A 的靜態(tài)屬性 Object.setPrototypeOf(Child, Parent);總結(jié)
javascript 由于歷史發(fā)展原因,繼承方式實(shí)際上是通過原型鏈屬性查找的方式,但正規(guī)的叫法不叫繼承而叫“委托”,ES6 的 class extends 關(guān)鍵字也不過是 ES5 的語法糖。所以,了解 JS 的原型和原型鏈非常重要,詳情請(qǐng)翻看我之前的文章《JavaScript原型與原型鏈》
參考:
《JavaScript 高級(jí)程序設(shè)計(jì)》
2019/02/10 @Starbucks
歡迎關(guān)注我的個(gè)人公眾號(hào)“謝南波”,專注分享原創(chuàng)文章。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/101586.html
摘要:之面向?qū)ο髮?duì)象類型數(shù)據(jù)類型分六類簡單類型五種復(fù)雜類型其中也屬于基本類型。 js之面向?qū)ο?OOP) js對(duì)象類型(Object) js數(shù)據(jù)類型分六類,簡單類型:Undefined,Null,Bollean,Number,String五種,復(fù)雜類型:Object.其中Undefined、Null、Boolean、Number也屬于基本類型。Object、Array和Function則屬...
摘要:道阻且長啊前端面試總結(jié)前端面試筆試面試騰訊一面瀏覽器工作原理瀏覽器的主要組件包括用戶界面包括地址欄后退前進(jìn)按鈕書簽?zāi)夸洖g覽器引擎用來查詢及操作渲染引擎的接口渲染引擎渲染界面和是基于兩種渲染引擎構(gòu)建的,使用自主研發(fā)的渲染引擎,和都使用網(wǎng)絡(luò)用來 道阻且長啊TAT(前端面試總結(jié)) 前端 面試 筆試 面試 騰訊一面 1.瀏覽器工作原理 瀏覽器的主要組件包括: 用戶界面- 包括地址欄、后退/前...
摘要:道阻且長啊前端面試總結(jié)前端面試筆試面試騰訊一面瀏覽器工作原理瀏覽器的主要組件包括用戶界面包括地址欄后退前進(jìn)按鈕書簽?zāi)夸洖g覽器引擎用來查詢及操作渲染引擎的接口渲染引擎渲染界面和是基于兩種渲染引擎構(gòu)建的,使用自主研發(fā)的渲染引擎,和都使用網(wǎng)絡(luò)用來 道阻且長啊TAT(前端面試總結(jié)) 前端 面試 筆試 面試 騰訊一面 1.瀏覽器工作原理 瀏覽器的主要組件包括: 用戶界面- 包括地址欄、后退/前...
摘要:道阻且長啊前端面試總結(jié)前端面試筆試面試騰訊一面瀏覽器工作原理瀏覽器的主要組件包括用戶界面包括地址欄后退前進(jìn)按鈕書簽?zāi)夸洖g覽器引擎用來查詢及操作渲染引擎的接口渲染引擎渲染界面和是基于兩種渲染引擎構(gòu)建的,使用自主研發(fā)的渲染引擎,和都使用網(wǎng)絡(luò)用來 道阻且長啊TAT(前端面試總結(jié)) 前端 面試 筆試 面試 騰訊一面 1.瀏覽器工作原理 瀏覽器的主要組件包括: 用戶界面- 包括地址欄、后退/前...
閱讀 3014·2020-01-08 12:17
閱讀 1999·2019-08-30 15:54
閱讀 1156·2019-08-30 15:52
閱讀 2040·2019-08-29 17:18
閱讀 1051·2019-08-29 15:34
閱讀 2466·2019-08-27 10:58
閱讀 1867·2019-08-26 12:24
閱讀 374·2019-08-23 18:23