从babel实现es6类的继承来深入理解js的原型及继承
生活随笔
收集整理的這篇文章主要介紹了
从babel实现es6类的继承来深入理解js的原型及继承
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
先聊個5毛錢的背景吧
自從有了babel這一個利器之后,es6現(xiàn)在已經(jīng)被廣泛的使用。JavaScript 類實質上是 JavaScript 現(xiàn)有的基于原型的繼承的語法糖。類語法不會為JavaScript引入新的面向對象的繼承模型。也就是說babel無論是實現(xiàn)類,還是實現(xiàn)繼承,本質上都是基于原型及原型鏈的,那我我們就順這這個思路,一步一步往下走,直到揭開babel是如何實現(xiàn)類及類的繼承的。
原型和原型鏈
javascript中原型的概念比較抽象,不是很好理解,我們還是老老實實上代碼,用代碼來舉例說明。
function Person (name, age, job) {this.name = name;this.age = age;this.job = job; } let p1 = new Person('張三', 18, '前端攻城獅') 復制代碼我們創(chuàng)建了一個Person的構造函數(shù),并用new 創(chuàng)建了一個該對象的實例對象p1。
- 在js中,每一個函數(shù)都有一個prototype屬性。
- 每一個js對象(除null)外都有一個__proto__的屬性。 那么問題來啦,函數(shù)Person的prototype的屬性指向的是什么呢?是不是Person的原型呢?我們在chrome中輸入如下代碼
我們發(fā)現(xiàn)Person.prototype指向一個對象,實際上這個對象就是p1的原型。如圖: 因此,我們也可以用下面的這個圖來表示他們之間的關系 - 前面我們說過,Person.prototype指向一個對象,那么這個對象中都有什么呢?我們再在chrome中輸入如下代碼如圖: 我們發(fā)現(xiàn)Person.prototype的確是一個對象,這個對象里面有兩個屬性:constrcutor, 和__proto__這兩個屬性。constructor指向Person(),也說明js對象果真都有一個__proto__的屬性啊。那問題來啦constructor到底和Person是什么關系呢?實際上,constructor屬性指向的就是構造函數(shù)本身。有圖有真相
- 每個原型上都有一個constructor的屬性指向關聯(lián)的構造函數(shù)。他們的關系如下: 前面我們發(fā)現(xiàn)Person.prototype也有__proto__屬性,那Person.prototype.__proto__是什么呢?換言之,原型的原型是什么呢。先來一張圖 我們發(fā)現(xiàn)它只有constructor屬性,該屬性指向Object(),沒有__proto__屬性。是不是很有意思。那我們再做個實驗吧。如圖
- 原型對象就是通過Object構造函數(shù)生成的,實例的__proto__指向構造函數(shù)的prototype,Object構造函數(shù)的原型的__proto__指向null
- 我們用一張圖總結一下吧: 實際上這個就是原型鏈啦。
ES5中的繼承
前面我們聊了聊原型和原型鏈,其實就是為了我們聊繼承做鋪墊的。廢話不多說,我們上代碼吧:
// 我們定義一個動物類,里面有一個類型的屬性 function Animal (type) {this.type = type } // 動物都有吃東西的方法 Animal.prototype.eat = function () {console.log('我在吃東西') } // 我們來個小貓咪類吧 function Cat () {} // 現(xiàn)在我們要實現(xiàn)讓Cat繼承Animal的屬性和方法,該怎么做呢? 復制代碼- 第一種方法
結果如圖
我們發(fā)現(xiàn)無法繼承父類的屬性。我們改造一下,借助call方法,代碼入下: function Animal (type) {this.type = type } Animal.prototype.eat = function () {console.log('我在吃東西') } function Cat (type) {Animal.call(this, type) } Cat.prototype = new Animal() let cat = new Cat('喵咪') console.log(cat.eat()) console.log(cat.type) 復制代碼運行結果如下:
nice!!!似乎完美的解決了問題,但真的是這樣么? 我就不賣關子啦,實際上是有問題的,有什么問題呢?我們上代碼: function Animal (type, val) {this.type = typethis.sum = [4,5,6] // 隨便給的屬性啊,為了說明問題。this.setSum = function () {this.sum.push(val)} } Animal.prototype.eat = function () {console.log('我在吃東西') } function Cat (type, val) {Animal.call(this, type, val) } Cat.prototype = new Animal() let cat = new Cat('喵咪', 1) let cat2 = new Cat('貓咪2', 2) console.log(cat.setSum()) console.log(cat2.setSum()) console.log(cat.sum) console.log(cat2.sum) 復制代碼運行結果如圖:
發(fā)現(xiàn)了沒有,圖中setSum方法和sum屬性都是父類定義的,但是子類可以調用。所以這個不是我們想要的結果;還有一點就是Cat的constructor屬性,此時指向的并不是Cat而是Animal。那應該怎么解決呢?- 第二種方法: 這個時候我們就不得不拿出我們的終極神器啦。它就是ES5的Object.create()方法。代碼如下:
代碼運行如下:
這樣我們就比較完美的解決了繼承的問題。babel對es6類的實現(xiàn)
在背景中我們已經(jīng)聊到了es6中類的繼承實際上是一個語法糖,現(xiàn)在我們就想辦法撥開這顆糖。
- es6類重寫上面代碼
- babel中類的實現(xiàn)
babel中類的實現(xiàn)主要是三個步驟:- 步驟一: 構造函數(shù)的實現(xiàn),這部分的實現(xiàn)與我們普通的構造函數(shù)的區(qū)別就是,通過一個自執(zhí)行函數(shù)包裹,代碼如下 var Animal = function () {function Animal (type) {this.type = typethis.getType = function () {return this.type}}return Animal }()var Cat = function () {function Cat (talk) {this.talk = talkthis.getTalk = function () {return this.talk}}return Cat}() 復制代碼
- 步驟二: 如下代碼: function Animal() {return {name: '牛逼',age: 18}}let animal = new Animal() console.log(animal) // { name: '牛逼', age: 18} 復制代碼因此babel中對此做了校驗,這就是第二步要干的事情。代碼如下: var _classCallCheck = function (instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("不能返回一個對象")}}// 嗯,你沒有看錯就是這么幾行代碼var Animal = function () {function Animal (type) {_classCallCheck(this, Animal)this.type = type}}()...此處省略喵咪類 復制代碼
- 步驟三: 原型上方法的添加。前面兩步主要是對構造函數(shù)的實現(xiàn)及校驗,步驟三就是把原型上的方法添加到對應的類上。這里我們主要要聊聊babel是如何對 es6中類的方法的處理,也就是 class Animal {eat () {console.log('aaa')}sleep () {console.log('bbb')}} 復制代碼如上面代碼中的eat方法和sleep方法的處理。那怎么能把方法添加到一個對象上呢?沒錯,Object.defineProperty()這個方法就是用來干這件事情的,babel中也是用他來處理的。回到我們的話題,在babel中,它是通過_createClass這個函數(shù)來處理的。廢話不多說我們上代碼: /* _createClass()這個函數(shù)接受兩個參數(shù),一個是這個類的構造函數(shù),即給哪個類添加方法。第二個參數(shù)是一個數(shù)組對象,類似這樣嬸的[{key: 'eat', val: function eat () { console.log('aaa') }}] 是不是恍然大悟?那接下來我們就實現(xiàn)一下吧*/function definePropties (target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ('value' in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor)}}var _createClass = function () {return function (Constructor, protoProps, staticProps) {// 添加原型上的方法if (protoProps) definePropties(Constructor.prototype, protoProps)// 添加靜態(tài)的方法if (staticProps) definePropties(Constructor, staticProps) return Constructor}}()_createClass(Animal,[{key: 'eat',value: function () {console.log('aaa')}}]) 復制代碼
babel中繼承的實現(xiàn)
接下來,終于到了我們的大boss啦,上面我們聊了聊babel中是怎么處理es6中類的,這節(jié)我們就聊聊babel中是怎么處理es6中的繼承。
- babel中繼承的實現(xiàn) babel中繼承的實現(xiàn)的思路基本跟我們前面聊的es5的繼承思路基本一致。下面我們來聊一聊。
好啦,這個話題就聊到這里,歡迎大家拍磚啊。
總結
以上是生活随笔為你收集整理的从babel实现es6类的继承来深入理解js的原型及继承的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第5章-Vue.js交互及生命周期练习
- 下一篇: os、sys模块