詳解如何用babel轉(zhuǎn)換es6的class語(yǔ)法
babel是一個(gè)轉(zhuǎn)碼器,目前開(kāi)發(fā)react、vue項(xiàng)目都要使用到它。它可以把es6+的語(yǔ)法轉(zhuǎn)換為es5,也可以轉(zhuǎn)換JSX等語(yǔ)法。
我們?cè)陧?xiàng)目中都是通過(guò)配置插件和預(yù)設(shè)(多個(gè)插件的集合)來(lái)轉(zhuǎn)換特定代碼,例如env、stage-0等。
實(shí)際上babel可以通過(guò)自定義插件的方式實(shí)現(xiàn)任何代碼的轉(zhuǎn)換,接下來(lái)我們通過(guò)一個(gè)“把es6的 class
轉(zhuǎn)換為es5”的例子來(lái)了解一下babel。
內(nèi)容如下:
webpack環(huán)境配置
大家應(yīng)該都配置過(guò)babel-core這個(gè)loader,它的作用是提供babel的核心Api,實(shí)際上我們的代碼轉(zhuǎn)換都是通過(guò)插件來(lái)實(shí)現(xiàn)的。
接下來(lái)我們不用第三方的插件,自己實(shí)現(xiàn)一個(gè)es6類(lèi)轉(zhuǎn)換插件。先執(zhí)行以下幾步初始化一個(gè)項(xiàng)目:
- npm install webpack webpack-cli babel-core -D
- 新建一個(gè)webpack.config.js
- 配置webpack.config.js
如果我們的插件名字想叫transform-class,需要在webpack配置中做如下配置:
接下來(lái)我們?cè)趎ode_modules中新建一個(gè)babel-plugin-transform-class的文件夾來(lái)寫(xiě)插件的邏輯(如果是真實(shí)項(xiàng)目,你需要編寫(xiě)這個(gè)插件并發(fā)布到npm倉(cāng)庫(kù)),如下圖:
紅色區(qū)域是我新建的文件夾,它上面的是一個(gè)標(biāo)準(zhǔn)的插件的項(xiàng)目結(jié)構(gòu),為了方便我只寫(xiě)了核心的index.js文件。
如何編寫(xiě)bable插件
babel插件其實(shí)是通過(guò)AST(抽象語(yǔ)法樹(shù))實(shí)現(xiàn)的。
babel幫助我們把js代碼轉(zhuǎn)換為AST,然后允許我們修改,最后再把它轉(zhuǎn)換成js代碼。
那么就涉及到兩個(gè)問(wèn)題:js代碼和AST之間的映射關(guān)系是什么?如何替換或者新增AST?
好,先介紹一個(gè)工具:astexplorer.net:
這個(gè)工具可以把一段代碼轉(zhuǎn)換為AST:
如圖,我們寫(xiě)了一個(gè)es6的類(lèi),然后網(wǎng)頁(yè)的右邊幫我們生成了一個(gè)AST,其實(shí)就是把每一行代碼變成了一個(gè)對(duì)象,這樣我們就實(shí)現(xiàn)了一個(gè)映射。
再介紹一個(gè)文檔: babel-types :
這是創(chuàng)建AST節(jié)點(diǎn)的api文檔。
比如,我們想創(chuàng)建一個(gè)類(lèi),先到astexplorer.net中轉(zhuǎn)換,發(fā)現(xiàn)類(lèi)對(duì)應(yīng)的AST類(lèi)型是 ClassDeclaration
。好,我們?nèi)ノ臋n中搜索,發(fā)現(xiàn)調(diào)用下面的api就可以了:
創(chuàng)建其他語(yǔ)句也是一樣的道理,有了上面這兩個(gè)東西,我們可以做任何轉(zhuǎn)換了。
下面我們開(kāi)始真正編寫(xiě)一個(gè)插件,分為以下幾步:
- 在index.js中export一個(gè)函數(shù)
- 函數(shù)中返回一個(gè)對(duì)象,對(duì)象有一個(gè)visitor參數(shù)(必須叫visitor)
- 通過(guò)astexplorer.net查詢(xún)出
class
對(duì)應(yīng)的AST節(jié)點(diǎn)為ClassDeclaration
- 在vistor中設(shè)置一個(gè)捕獲函數(shù)
ClassDeclaration
,意思是我要捕獲js代碼中所有ClassDeclaration
節(jié)點(diǎn) - 編寫(xiě)邏輯代碼,完成轉(zhuǎn)換
module.exports = function ({ types: t }) { return { visitor: { ClassDeclaration(path) { //在這里完成轉(zhuǎn)換 } } }; }
代碼中有兩個(gè)參數(shù),第一個(gè) {types:t}
東西是從參數(shù)中解構(gòu)出變量t,它其實(shí)就是babel-types文檔中的t(下圖紅框),它是用來(lái)創(chuàng)建節(jié)點(diǎn)的:
第二個(gè)參數(shù) path
,它是捕獲到的節(jié)點(diǎn)對(duì)應(yīng)的信息,我們可以通過(guò) path.node
獲得這個(gè)節(jié)點(diǎn)的AST,在這個(gè)基礎(chǔ)上進(jìn)行修改就能完成了我們的目標(biāo)。
如何把es6的class轉(zhuǎn)換為es5的類(lèi)
上面都是預(yù)備工作,真正的邏輯從現(xiàn)在才開(kāi)始,我們先考慮兩個(gè)問(wèn)題:
我們要做如下轉(zhuǎn)換,首先把es6的類(lèi),轉(zhuǎn)換為es5的類(lèi)寫(xiě)法(也就是普通函數(shù)),我們觀察到,很多代碼是可以復(fù)用的,包括函數(shù)名字、函數(shù)內(nèi)部的代碼塊等。
如果不定義class中的 constructor
方法,JavaScript引擎會(huì)自動(dòng)為它添加一個(gè)空的 constructor()
方法,這需要我們做兼容處理。
接下來(lái)我們開(kāi)始寫(xiě)代碼,思路是:
- 拿到老的AST節(jié)點(diǎn)
- 創(chuàng)建一個(gè)數(shù)組用來(lái)盛放新的AST節(jié)點(diǎn)(雖然原class只是一個(gè)節(jié)點(diǎn),但是替換后它會(huì)被若干個(gè)函數(shù)節(jié)點(diǎn)取代) 初始化默認(rèn)的
constructor
節(jié)點(diǎn)(上文提到,class中有可能沒(méi)有定義constructor) - 循環(huán)老節(jié)點(diǎn)的AST對(duì)象(會(huì)循環(huán)出若干個(gè)函數(shù)節(jié)點(diǎn))
- 判斷函數(shù)的類(lèi)型是不是
constructor
,如果是,通過(guò)取到數(shù)據(jù)創(chuàng)建一個(gè)普通函數(shù)節(jié)點(diǎn),并更新默認(rèn)constructor
節(jié)點(diǎn) - 處理其余不是
constructor
的節(jié)點(diǎn),通過(guò)數(shù)據(jù)創(chuàng)建prototype
類(lèi)型的函數(shù),并放到es5Fns
中 - 循環(huán)結(jié)束,把
constructor
節(jié)點(diǎn)也放到es5Fns
中 - 判斷es5Fns的長(zhǎng)度是否大于1,如果大于1使用
replaceWithMultiple
這個(gè)API更新AST
module.exports = function ({ types: t }) { return { visitor: { ClassDeclaration(path) { //拿到老的AST節(jié)點(diǎn) let node = path.node let className = node.id.name let classInner = node.body.body //創(chuàng)建一個(gè)數(shù)組用來(lái)成盛放新生成AST let es5Fns = [] //初始化默認(rèn)的constructor節(jié)點(diǎn) let newConstructorId = t.identifier(className) let constructorFn = t.functionDeclaration(newConstructorId, [t.identifier('')], t.blockStatement([]), false, false) //循環(huán)老節(jié)點(diǎn)的AST對(duì)象 for (let i = 0; i < classInner.length; i++) { let item = classInner[i] //判斷函數(shù)的類(lèi)型是不是constructor if (item.kind == 'constructor') { let constructorParams = item.params.length ? item.params[0].name : [] let newConstructorParams = t.identifier(constructorParams) let constructorBody = classInner[i].body constructorFn = t.functionDeclaration(newConstructorId, [newConstructorParams], constructorBody, false, false) } //處理其余不是constructor的節(jié)點(diǎn) else { let protoTypeObj = t.memberExpression(t.identifier(className), t.identifier('prototype'), false) let left = t.memberExpression(protoTypeObj, t.identifier(item.key.name), false) //定義等號(hào)右邊 let prototypeParams = classInner[i].params.length ? classInner[i].params[i].name : [] let newPrototypeParams = t.identifier(prototypeParams) let prototypeBody = classInner[i].body let right = t.functionExpression(null, [newPrototypeParams], prototypeBody, false, false) let protoTypeExpression = t.assignmentExpression("=", left, right) es5Fns.push(protoTypeExpression) } } //循環(huán)結(jié)束,把constructor節(jié)點(diǎn)也放到es5Fns中 es5Fns.push(constructorFn) //判斷es5Fns的長(zhǎng)度是否大于1 if (es5Fns.length > 1) { path.replaceWithMultiple(es5Fns) } else { path.replaceWith(constructorFn) } } } }; }
優(yōu)化繼承
其實(shí),類(lèi)還涉及到繼承,思路也不復(fù)雜,就是判斷AST中沒(méi)有 superClass
屬性,如果有的話,我們需要多添加一行代碼 Bird.prototype = Object.create(Parent)
,當(dāng)然別忘了處理 super
關(guān)鍵字。
打包后代碼
運(yùn)行 npm start
打包后,我們看到打包后的文件里 class
語(yǔ)法已經(jīng)成功轉(zhuǎn)換為一個(gè)個(gè)的es5函數(shù)。
結(jié)尾
現(xiàn)在一個(gè)類(lèi)轉(zhuǎn)換器就寫(xiě)完了,希望能對(duì)大家了解babel有一點(diǎn)幫助。也希望大家多多支持腳本之家。
- ES6 javascript中class類(lèi)的get與set用法實(shí)例分析
- ES6新特性之類(lèi)(Class)和繼承(Extends)相關(guān)概念與用法分析
- ES6 javascript中Class類(lèi)繼承用法實(shí)例詳解
- ES6中Class類(lèi)的靜態(tài)方法實(shí)例小結(jié)
- ES6中class類(lèi)用法實(shí)例淺析
- ES6 javascript中class靜態(tài)方法、屬性與實(shí)例屬性用法示例
- JavaScript ES6中CLASS的使用詳解
- 深入淺析ES6 Class 中的 super 關(guān)鍵字
- ES6 Class中實(shí)現(xiàn)私有屬性的一些方法總結(jié)
- es6中class類(lèi)靜態(tài)方法,靜態(tài)屬性,實(shí)例屬性,實(shí)例方法的理解與應(yīng)用分析
相關(guān)文章
Bootstrap組件學(xué)習(xí)之導(dǎo)航、標(biāo)簽、面包屑導(dǎo)航(精品)
這篇文章主要介紹了Bootstrap組件學(xué)習(xí)之導(dǎo)航、標(biāo)簽、面包屑導(dǎo)航(精品)的相關(guān)資料,需要的朋友可以參考下2016-05-05JS實(shí)現(xiàn)簡(jiǎn)單九宮格抽獎(jiǎng)
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)簡(jiǎn)單九宮格抽獎(jiǎng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06javascript實(shí)現(xiàn)tabs選項(xiàng)卡切換效果(自寫(xiě)原生js)
常用的頁(yè)面效果有彈出層效果,無(wú)縫滾動(dòng)效果,選項(xiàng)卡切換效果,接下來(lái)與大家分享一款自己用原生javascript寫(xiě)的選項(xiàng)卡切換效果,感興趣的朋友可以參考下哈2013-03-03微信小程序?qū)崿F(xiàn)展示評(píng)分結(jié)果功能
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)展示評(píng)分結(jié)果功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02JS實(shí)現(xiàn)的集合去重,交集,并集,差集功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)的集合去重,交集,并集,差集功能,結(jié)合實(shí)例形式分析了javascript基于數(shù)組實(shí)現(xiàn)的集合去重、交集、并集、差集等相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-03-03JavaScript樹(shù)形數(shù)據(jù)結(jié)構(gòu)處理
這篇文章主要介紹了JavaScript樹(shù)形數(shù)據(jù)結(jié)構(gòu)處理,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-07-07如何將網(wǎng)頁(yè)表格內(nèi)容導(dǎo)入excel
這篇文章主要介紹了如何將網(wǎng)頁(yè)表格內(nèi)容導(dǎo)入excel,需要的朋友可以參考下2014-02-02JavaScript中函數(shù)(Function)的apply與call理解
這篇文章主要介紹了JavaScript中函數(shù)(Function)的apply與call理解,本文講解了JavaScript函數(shù)調(diào)用分為4中模式以及通過(guò)apply和call實(shí)現(xiàn)擴(kuò)展和繼承兩方面,需要的朋友可以參考下2015-07-07