詳解如何用babel轉(zhuǎn)換es6的class語法
babel是一個轉(zhuǎn)碼器,目前開發(fā)react、vue項目都要使用到它。它可以把es6+的語法轉(zhuǎn)換為es5,也可以轉(zhuǎn)換JSX等語法。
我們在項目中都是通過配置插件和預設(多個插件的集合)來轉(zhuǎn)換特定代碼,例如env、stage-0等。
實際上babel可以通過自定義插件的方式實現(xiàn)任何代碼的轉(zhuǎn)換,接下來我們通過一個“把es6的 class 轉(zhuǎn)換為es5”的例子來了解一下babel。
內(nèi)容如下:
webpack環(huán)境配置
大家應該都配置過babel-core這個loader,它的作用是提供babel的核心Api,實際上我們的代碼轉(zhuǎn)換都是通過插件來實現(xiàn)的。
接下來我們不用第三方的插件,自己實現(xiàn)一個es6類轉(zhuǎn)換插件。先執(zhí)行以下幾步初始化一個項目:
- npm install webpack webpack-cli babel-core -D
- 新建一個webpack.config.js
- 配置webpack.config.js
如果我們的插件名字想叫transform-class,需要在webpack配置中做如下配置:

接下來我們在node_modules中新建一個babel-plugin-transform-class的文件夾來寫插件的邏輯(如果是真實項目,你需要編寫這個插件并發(fā)布到npm倉庫),如下圖:

紅色區(qū)域是我新建的文件夾,它上面的是一個標準的插件的項目結(jié)構(gòu),為了方便我只寫了核心的index.js文件。
如何編寫bable插件
babel插件其實是通過AST(抽象語法樹)實現(xiàn)的。
babel幫助我們把js代碼轉(zhuǎn)換為AST,然后允許我們修改,最后再把它轉(zhuǎn)換成js代碼。
那么就涉及到兩個問題:js代碼和AST之間的映射關(guān)系是什么?如何替換或者新增AST?
好,先介紹一個工具:astexplorer.net:
這個工具可以把一段代碼轉(zhuǎn)換為AST:

如圖,我們寫了一個es6的類,然后網(wǎng)頁的右邊幫我們生成了一個AST,其實就是把每一行代碼變成了一個對象,這樣我們就實現(xiàn)了一個映射。
再介紹一個文檔: babel-types :
這是創(chuàng)建AST節(jié)點的api文檔。
比如,我們想創(chuàng)建一個類,先到astexplorer.net中轉(zhuǎn)換,發(fā)現(xiàn)類對應的AST類型是 ClassDeclaration 。好,我們?nèi)ノ臋n中搜索,發(fā)現(xiàn)調(diào)用下面的api就可以了:

創(chuàng)建其他語句也是一樣的道理,有了上面這兩個東西,我們可以做任何轉(zhuǎn)換了。
下面我們開始真正編寫一個插件,分為以下幾步:
- 在index.js中export一個函數(shù)
- 函數(shù)中返回一個對象,對象有一個visitor參數(shù)(必須叫visitor)
- 通過astexplorer.net查詢出
class對應的AST節(jié)點為ClassDeclaration - 在vistor中設置一個捕獲函數(shù)
ClassDeclaration,意思是我要捕獲js代碼中所有ClassDeclaration節(jié)點 - 編寫邏輯代碼,完成轉(zhuǎn)換
module.exports = function ({ types: t }) {
return {
visitor: {
ClassDeclaration(path) {
//在這里完成轉(zhuǎn)換
}
}
};
}
代碼中有兩個參數(shù),第一個 {types:t} 東西是從參數(shù)中解構(gòu)出變量t,它其實就是babel-types文檔中的t(下圖紅框),它是用來創(chuàng)建節(jié)點的:

第二個參數(shù) path ,它是捕獲到的節(jié)點對應的信息,我們可以通過 path.node 獲得這個節(jié)點的AST,在這個基礎上進行修改就能完成了我們的目標。
如何把es6的class轉(zhuǎn)換為es5的類
上面都是預備工作,真正的邏輯從現(xiàn)在才開始,我們先考慮兩個問題:
我們要做如下轉(zhuǎn)換,首先把es6的類,轉(zhuǎn)換為es5的類寫法(也就是普通函數(shù)),我們觀察到,很多代碼是可以復用的,包括函數(shù)名字、函數(shù)內(nèi)部的代碼塊等。
如果不定義class中的 constructor 方法,JavaScript引擎會自動為它添加一個空的 constructor() 方法,這需要我們做兼容處理。
接下來我們開始寫代碼,思路是:
- 拿到老的AST節(jié)點
- 創(chuàng)建一個數(shù)組用來盛放新的AST節(jié)點(雖然原class只是一個節(jié)點,但是替換后它會被若干個函數(shù)節(jié)點取代) 初始化默認的
constructor節(jié)點(上文提到,class中有可能沒有定義constructor) - 循環(huán)老節(jié)點的AST對象(會循環(huán)出若干個函數(shù)節(jié)點)
- 判斷函數(shù)的類型是不是
constructor,如果是,通過取到數(shù)據(jù)創(chuàng)建一個普通函數(shù)節(jié)點,并更新默認constructor節(jié)點 - 處理其余不是
constructor的節(jié)點,通過數(shù)據(jù)創(chuàng)建prototype類型的函數(shù),并放到es5Fns中 - 循環(huán)結(jié)束,把
constructor節(jié)點也放到es5Fns中 - 判斷es5Fns的長度是否大于1,如果大于1使用
replaceWithMultiple這個API更新AST
module.exports = function ({ types: t }) {
return {
visitor: {
ClassDeclaration(path) {
//拿到老的AST節(jié)點
let node = path.node
let className = node.id.name
let classInner = node.body.body
//創(chuàng)建一個數(shù)組用來成盛放新生成AST
let es5Fns = []
//初始化默認的constructor節(jié)點
let newConstructorId = t.identifier(className)
let constructorFn = t.functionDeclaration(newConstructorId, [t.identifier('')], t.blockStatement([]), false, false)
//循環(huán)老節(jié)點的AST對象
for (let i = 0; i < classInner.length; i++) {
let item = classInner[i]
//判斷函數(shù)的類型是不是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é)點
else {
let protoTypeObj = t.memberExpression(t.identifier(className), t.identifier('prototype'), false)
let left = t.memberExpression(protoTypeObj, t.identifier(item.key.name), false)
//定義等號右邊
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é)點也放到es5Fns中
es5Fns.push(constructorFn)
//判斷es5Fns的長度是否大于1
if (es5Fns.length > 1) {
path.replaceWithMultiple(es5Fns)
} else {
path.replaceWith(constructorFn)
}
}
}
};
}
優(yōu)化繼承
其實,類還涉及到繼承,思路也不復雜,就是判斷AST中沒有 superClass 屬性,如果有的話,我們需要多添加一行代碼 Bird.prototype = Object.create(Parent) ,當然別忘了處理 super 關(guān)鍵字。
打包后代碼
運行 npm start 打包后,我們看到打包后的文件里 class
語法已經(jīng)成功轉(zhuǎn)換為一個個的es5函數(shù)。
結(jié)尾
現(xiàn)在一個類轉(zhuǎn)換器就寫完了,希望能對大家了解babel有一點幫助。也希望大家多多支持腳本之家。
- ES6 javascript中class類的get與set用法實例分析
- ES6新特性之類(Class)和繼承(Extends)相關(guān)概念與用法分析
- ES6 javascript中Class類繼承用法實例詳解
- ES6中Class類的靜態(tài)方法實例小結(jié)
- ES6中class類用法實例淺析
- ES6 javascript中class靜態(tài)方法、屬性與實例屬性用法示例
- JavaScript ES6中CLASS的使用詳解
- 深入淺析ES6 Class 中的 super 關(guān)鍵字
- ES6 Class中實現(xiàn)私有屬性的一些方法總結(jié)
- es6中class類靜態(tài)方法,靜態(tài)屬性,實例屬性,實例方法的理解與應用分析
相關(guān)文章
javascript HTML+CSS實現(xiàn)經(jīng)典橙色導航菜單
這篇文章主要介紹了javascript HTML+CSS實現(xiàn)經(jīng)典橙色導航菜單的相關(guān)資料,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-02-02
nuxt+axios實現(xiàn)打包后動態(tài)修改請求地址的方法
這篇文章主要介紹了nuxt+axios實現(xiàn)打包后動態(tài)修改請求地址的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-04-04
fiv.js實現(xiàn)flv文件blob流實時播放的項目實踐
本文主要介紹了fiv.js實現(xiàn)flv文件blob流實時播放的項目實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-04-04
創(chuàng)建與框架無關(guān)的JavaScript插件
這篇文章主要介紹了創(chuàng)建與框架無關(guān)的JavaScript插件,幫助大家更好的理解和使用JavaScript,感興趣的朋友可以了解下2020-12-12

