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