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