JS繼承的一些見解

# JS繼承的一些見解 > js在es6之前的繼承是五花八門的。而且要在項目中靈活運用面向對象寫法也是有點彆扭,更多的時候還是覺得面向過程的寫法更爲簡單,效率也高。久而久之對js的繼承每隔一段時間就會理解出現困難。所以這次我要把對對象的理解寫下來,這樣應該就深刻一點了。 我們先來看看一個對象是怎麼生成的 ```javascript // 三種創建對象的方法 var obj = {} var obj2 = new Object() var obj3 = Object.create(null) // 創建一個空字符串對象 var obj4 = new String() obj.constructor === obj2.constructor // true obj.__proto__ === obj2.__proto__ === Object.prototype // true obj4.__proto__ === String.prototype // true obj4.__proto__.__proto__ === Object.prototype // ture ``` > 這三種方法,前面兩種是一樣的。它們創建的空對象都具有原型,而第三種方式創建的是一個真正意義上的空對象,它不繼承任何屬性;而obj4呢是一個字符串對象。看下圖的對比: 下面對象除了obj3都有個__proto__的隱性屬性,這個屬性指向的是該創建該實例的構造函數的原型。 ![js對象圖1](http://p0639a4mt.bkt.clouddn.com/1523691451000-012.jpg "js對象圖1") 這裏obj和obj2雖然是空對象,不過它們具有Object構造函數的屬性的方法,而obj4除了有obj擁有的屬性和方法,它還具備了String擁有的屬性和方法;而obj3是真正的空對象,它的__proto__指向的是null。 ##### 我們再將obj4全部展開看看: ![js對象圖2](http://p0639a4mt.bkt.clouddn.com/1523692671000-427.jpg "js對象圖2") ##### 再來看看它們之間的關係: ![js對象圖3](http://p0639a4mt.bkt.clouddn.com/1523695882000-212.png "js對象圖3") > 如上圖,我舉得例子是Object和String這兩個原生具有的構造器。它們的關係和我們平時寫的父類和子類之間的關係是一樣的,所有的類最終都會指向Object。 在es5的時代,我們用到的繼承最簡單的就是原型鏈繼承了 ```javascript // 我們先創建一個父類 function Super (name) { this.name = name this.type = '我是父類' } Super.prototype.move = function () { console.log(this.name + '在跑') } // 子類繼承父類 function Child (name) { this.name = name } Child.prototype = new Super() // 原型鏈繼承,子類原型的私有方法和屬性要在繼承之後添加,不然會被覆蓋 Child.prototype.constructor = Child // 修復對構造函數的指向 Child.prototype.eat = function () { // 做點啥 } ``` > 原型鏈繼承就是將子類的原型等於父類的實例,然後再添加子類原型的屬性和方法。這樣的缺點就是隻能繼承一個父類。而且在創建子類的實例的時候,不能傳參給父類。在做單一繼承而且父類不需要傳參的時候,這種寫法是最好的選擇了。 下面在介紹一種我個人覺得已經很不錯的繼承方式(組合繼承) ```javascript // 子類繼承父類 function Child (name) { Super.call(this) this.name = name } Child.prototype = new Super() Child.prototype.constructor = Child // 修復對構造函數的指向 Child.prototype.eat = function () { // 做點啥 } ``` >- 這種繼承方式就是粗暴的將父類Super構造函數利用對象冒充的方式將父類的實例屬性複製到子類裏。在用原型繼承的方式繼承父類原型的屬性和方法。 >- 這樣的繼承方式,你會發現子類的實例的屬性和__proto__下都有父類的屬性,而子類實例的把子類原型的覆蓋了。 >- 這樣的方式可以實現對多個父類(非原型部分)的繼承。因爲原型的繼承是鏈式的,所以只能繼承一個。 一般來說這種繼承方式已經很好了,代碼量少。繼承性好,弊端也沒有影響。這就是ES6之前的繼承。下面再來看看ES6是怎麼操作的。 ### ES6的class >- ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,作爲對象的模板。通過class關鍵字,可以定義類。 >- 基本上,ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。上面的代碼用 ES6 的class改寫,就是下面這樣。 上面這兩句話是引用阮一峯對ES6的class的簡介(Class 的基本語法)。阮大神的ECMAScript 6 入門真的是對es6初學者居家必備的好東西。我在這裏就談談我的理解好了。 ```javascript // 寫一個class class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3) // new一個實例 ``` 下面看下point內部是怎樣的,可以看出constructor,toString都是在“__proto__”內的。 ![js對象圖4](http://p0639a4mt.bkt.clouddn.com/1523869463000-173.png "js對象圖4") 在這裏會等價於構造函數的什麼寫法呢 ```javascript // 寫一個class function Point (x, y) { this.x = x this.y = y } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; } var point = new Point(2, 3) // new一個實例 ``` 在不考慮class靜態屬性的情況下,可以簡單理解爲constructor內this下的新屬性和方法都屬於構造函數內的,而constructor外的方法都是prototype下的。 >- 在看看class的繼承 ```javascript class ChildPoint extends Point { constructor (x, y, z) { super(x, y) this.z = z } toString () { return '(' + this.x + ', ' + this.y + ','+ this.z +')'; } changeX (x) { this.x = x } } var childPoint = new ChildPoint(2, 3, 4) // new一個ChildPoint的實例 console.log(childPoint.x) // 2 console.log(childPoint.y) // 3 console.log(childPoint.z) // 4 console.log(childPoint.toString()) // 2,3,4 childPoint.changeX(5) console.log(childPoint.x) // 5 console.log(childPoint.__proto__ === ChildPoint.prototype) // true console.log(childPoint.__proto__.__proto__ === Point.prototype) // true console.log(childPoint.constructor === ChildPoint) // true ``` 這裏class是通過extends繼承父類的,而子類的constructor方法內需要調用super()方法,這相當與調用了父類的constructor()方法。 下面看下childPoint的內部情況: ![js對象圖5](http://p0639a4mt.bkt.clouddn.com/1523875338000-822.png "js對象圖5") 從圖片和上面打印的情況可以看出,es6的繼承和組合繼承很是相似。唯一的不同點就是es6的繼承沒有組合繼承產生的多餘的一份實例屬性在原型裏。看起來很順眼。不過相對的es6的不能多繼承(雖然組合繼承只能多繼承多個構造函數,並不能繼承多個原型)。 其實組合繼承還可以進一步升級成es6繼承那個樣子的(寄生組合繼承): ```javascript // 我們先創建一個父類 function Super (name) { this.name = name this.type = '我是父類' } Super.prototype.move = function () { console.log(this.name + '在跑') } // 子類繼承父類 function Child (name) { Super.call(this) this.name = name } (function() { // 創建一個沒有Super實例的中間類 var MiddleSuper = function() {} MiddleSuper.prototype = Super.prototyoe // 跟Super共享原型 // 在這裏MiddleSuper和Super的區別就是有沒有實例了。 // 然後在正常進行組合繼承的操作 Child.prototype = new MiddleSuper() })() Child.prototype.constructor = Child // 修復對構造函數的指向 Child.prototype.eat = function () { // 做點啥 } ``` ![js對象圖6](http://p0639a4mt.bkt.clouddn.com/1523934606000-937.png "js對象圖6") 經過這一系列複雜的操作後我們就得到了一份和es6一樣的結構了。不過es6只需要一個extends,而es6之前我們就得長篇大論的寫才能實現,而且還特別繞。所以一句話,快去學es6吧。 >- 該文純屬個人見解,有什麼問題請指出。 本文參考了: >- 幻天芒的JS繼承的實現方式 >- 阮一峯的ECMAScript 6 入門
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章