掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
我們編寫業(yè)務(wù)代碼的時(shí)候,可能很少人會(huì)使用到AST,以至于大多數(shù)同學(xué)都不大了解AST。有的同學(xué)曾經(jīng)學(xué)過(guò),但是不去實(shí)踐的話,過(guò)段時(shí)間又忘的差不多了。看到這里,你會(huì)發(fā)現(xiàn)說(shuō)的就是你。聽說(shuō)貴圈現(xiàn)在寫文章都要編故事,時(shí)不時(shí)還要整點(diǎn)表情包。這是真的嗎?作為公司最頭鐵的前端,我就不放。

成都創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括蒲江縣網(wǎng)站建設(shè)、蒲江縣網(wǎng)站制作、蒲江縣網(wǎng)頁(yè)制作以及蒲江縣網(wǎng)絡(luò)營(yíng)銷策劃等。多年來(lái),我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,蒲江縣網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到蒲江縣省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
本文將通過(guò)以下幾個(gè)方面對(duì)AST進(jìn)行學(xué)習(xí)
1.基礎(chǔ)知識(shí)
2.實(shí)戰(zhàn)小例子
3.總結(jié)
AST是什么先貼下官方的解釋
為了方便大家理解抽象語(yǔ)法樹,來(lái)看看具體的例子。
- var tree = 'this is tree'
js 源代碼將會(huì)被轉(zhuǎn)化成下面的抽象語(yǔ)法樹
- {
- "type": "Program",
- "start": 0,
- "end": 25,
- "body": [
- {
- "type": "VariableDeclaration",
- "start": 0,
- "end": 25,
- "declarations": [
- {
- "type": "VariableDeclarator",
- "start": 4,
- "end": 25,
- "id": {
- "type": "Identifier",
- "start": 4,
- "end": 8,
- "name": "tree"
- },
- "init": {
- "type": "Literal",
- "start": 11,
- "end": 25,
- "value": "this is tree",
- "raw": "'this is tree'"
- }
- }
- ],
- "kind": "var"
- }
- ],
- "sourceType": "module"
- }
可以看到一條語(yǔ)句由若干個(gè)詞法單元組成。這個(gè)詞法單元就像 26 個(gè)字母。創(chuàng)造出個(gè)十幾萬(wàn)的單詞,通過(guò)不同單詞的組合又能寫出不同內(nèi)容的文章。
至于有哪些詞法單元可點(diǎn)擊查看AST 對(duì)象文檔 或者 參考掘金大佬的文章高級(jí)前端基礎(chǔ)-JavaScript 抽象語(yǔ)法樹 AST里面列舉了語(yǔ)法樹節(jié)點(diǎn)與解釋。
推薦一個(gè)工具 在線 ast 轉(zhuǎn)換器??梢栽谶@個(gè)網(wǎng)站上,親自嘗試下轉(zhuǎn)換。點(diǎn)擊語(yǔ)句中的詞,右邊的抽象語(yǔ)法樹節(jié)點(diǎn)便會(huì)被選中,如下圖:
看到這里,你應(yīng)該已經(jīng)知道抽象語(yǔ)法樹大致長(zhǎng)什么樣了。那么AST又是如何生成的呢?
AST 整個(gè)解析過(guò)程分為兩個(gè)步驟
還是以上面var tree = 'this is tree'為例
先經(jīng)過(guò)詞法分析,掃描輸入的源代碼字符串,生成一系列的詞法單元 (tokens)。這些詞法單元包括數(shù)字,標(biāo)點(diǎn)符號(hào),運(yùn)算符等
語(yǔ)法分析階段就會(huì)將上一階段生成的 tokens 列表轉(zhuǎn)換為如下圖所示的 AST(我把start、end字段去掉了不用在意)
鄭重聲明:我周某人語(yǔ)文很少及格,大致意思能理解就好。
例子:"它是豬。"
先經(jīng)過(guò)詞法分析,掃描輸入的源代碼字符串,生成一系列的詞法單元 (tokens)。這些詞法單元包括數(shù)字,標(biāo)點(diǎn)符號(hào),運(yùn)算符等
語(yǔ)法分析階段就會(huì)將上一階段生成的 tokens 列表轉(zhuǎn)換為如下圖所示的 AST
JavaScript Parser,把 js 源碼轉(zhuǎn)化為抽象語(yǔ)法樹的解析器。
源代碼:
- function fn() {
- console.log('debugger')
- debugger;
- }
根據(jù)前面學(xué)過(guò)的知識(shí)點(diǎn),我們先腦海中意淫下如何去掉這個(gè)debugger
將源代碼拷貝到 在線 ast 轉(zhuǎn)換器 中,并點(diǎn)擊左邊區(qū)域的debugger,可以看到左邊的debugger節(jié)點(diǎn)就被選中了。所以只要把圖中選中的debugger抽象語(yǔ)法樹節(jié)點(diǎn)刪除就行了。
這個(gè)例子比較簡(jiǎn)單,直接上代碼。
這個(gè)例子我使用@babel/parser、@babel/traverse、@babel/generator,它們的作用分別是解析、轉(zhuǎn)換、生成。
- const parser = require('@babel/parser');
- const traverse = require("@babel/traverse");
- const generator = require("@babel/generator");
- // 源代碼
- const code = `
- function fn() {
- console.log('debugger')
- debugger;
- }
- `;
- // 1. 源代碼解析成 ast
- const ast = parser.parse(code);
- // 2. 轉(zhuǎn)換
- const visitor = {
- // traverse 會(huì)遍歷樹節(jié)點(diǎn),只要節(jié)點(diǎn)的 type 在 visitor 對(duì)象中出現(xiàn),變化調(diào)用該方法
- DebuggerStatement(path) {
- // 刪除該抽象語(yǔ)法樹節(jié)點(diǎn)
- path.remove();
- }
- }
- traverse.default(ast, visitor);
- // 3. 生成
- const result = generator.default(ast, {}, code);
- console.log(result.code)
- // 4. 日志輸出
- // function fn() {
- // console.log('debugger');
- // }
babel核心邏輯處理都在visitor里。traverse會(huì)遍歷樹節(jié)點(diǎn),只要節(jié)點(diǎn)的type在visitor對(duì)象中出現(xiàn),便會(huì)調(diào)用該type對(duì)應(yīng)的方法,在方法中調(diào)用path.remove()將當(dāng)前節(jié)點(diǎn)刪除。demo中使用到的path的一些api可以參考babel-handbook。
我們有時(shí)候在函數(shù)里打了日志,但是又想在控制臺(tái)直觀的看出是哪個(gè)函數(shù)中打的日志,這個(gè)時(shí)候就可以使用AST,去解析、轉(zhuǎn)換、生成最后想要的代碼。
源代碼:
- function funA() {
- console.log(1)
- }
- // 轉(zhuǎn)換成
- function funA() {
- console.log('from function funA:', 1)
- }
在編碼之前,我們先理清思路,再下手也不遲。這個(gè)時(shí)候就需要借助 在線 ast 轉(zhuǎn)換器來(lái)分析了。
通過(guò)工具發(fā)現(xiàn)想要實(shí)現(xiàn)這個(gè)案例只需要往arguments前面插入段節(jié)點(diǎn)就可以了。
這里也像例子 1 一樣先梳理下思路
將 js 代碼解析成 ast 與 將 ast 生成 js 代碼與去 debugger 例子一致,這里將不再描述。
首先監(jiān)聽CallExpression遍歷
- const visitor = {
- CallExpression(path) {
- // console.log(path)
- }
- }
觀察 在線 ast 轉(zhuǎn)換器 解析后的樹,我們只要判斷path 的 callee中存在對(duì)象 console 及屬性 property 。就可以往當(dāng)前的 path 的 arguments unshift 一個(gè) StringLiteral
這里的 types 對(duì)象是使用了一個(gè)新包 @babel/types , 用來(lái)判斷類型。
上面用到的isMemberExpression,isIdentifier,getFunctionParent,stringLiteral都可以在babel-handbook文檔中找到,本文就不解釋了。
- const visitor = {
- // 當(dāng)遍歷到 CallExpression 時(shí)候觸發(fā)
- CallExpression(path) {
- const callee = path.node.callee;
- // 判斷當(dāng)前當(dāng)前執(zhí)行的函數(shù)是否是組合表達(dá)式
- if (types.isMemberExpression(callee)) {
- const { object, property } = callee;
- if (types.isIdentifier(object, { name: 'console' }) && types.isIdentifier(property, { name: 'log' })) {
- // 查找最接近的父函數(shù)或程序
- const parent = path.getFunctionParent();
- const parentFunName = parent.node.id.name;
- path.node.arguments.unshift(types.stringLiteral(`from function ${parentFunName}`))
- }
- }
- }
- }
就像前言所說(shuō)的,我們的日常工作中很少會(huì)去使用AST,以至于大多數(shù)同學(xué)都不大了解AST。但了解了 AST 可以幫助我們更好地理解開發(fā)工具、編譯器的原理,并產(chǎn)出提高代碼效率的工具。還記得我在之前的前端小組遇到一個(gè)問(wèn)題,我們項(xiàng)目是SSR項(xiàng)目,在服務(wù)端執(zhí)行的生命周期不允許出現(xiàn)客戶端才能執(zhí)行的代碼。但是小組成員有時(shí)候無(wú)意的寫了,導(dǎo)致服務(wù)端渲染降級(jí)。在學(xué)習(xí)AST之前,我為了解決這個(gè)問(wèn)題,寫了個(gè)loader通過(guò)正則去匹配校驗(yàn),當(dāng)時(shí)可真是逼死我了,正則需要去適配各種場(chǎng)景。后面我學(xué)習(xí)了AST了之后,編寫了個(gè)eslint插件實(shí)現(xiàn)了客戶端代碼校驗(yàn)。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流