自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

JavaScript語(yǔ)法樹(shù)與代碼轉(zhuǎn)化實(shí)踐

開(kāi)發(fā) 前端
JavaScript 語(yǔ)法樹(shù)與代碼轉(zhuǎn)化實(shí)踐 歸納于筆者的現(xiàn)代 JavaScript 開(kāi)發(fā):語(yǔ)法基礎(chǔ)與實(shí)踐技巧系列文章中。本文引用的參考資料聲明于 JavaScript 學(xué)習(xí)與實(shí)踐資料索引中,特別需要聲明是部分代碼片引用自 Babel Handbook 開(kāi)源手冊(cè);也歡迎關(guān)注前端每周清單系列獲得一手資訊。

JavaScript 語(yǔ)法樹(shù)與代碼轉(zhuǎn)化實(shí)踐 歸納于筆者的現(xiàn)代 JavaScript 開(kāi)發(fā):語(yǔ)法基礎(chǔ)與實(shí)踐技巧系列文章中。本文引用的參考資料聲明于 JavaScript 學(xué)習(xí)與實(shí)踐資料索引中,特別需要聲明是部分代碼片引用自 Babel Handbook 開(kāi)源手冊(cè);也歡迎關(guān)注前端每周清單系列獲得一手資訊。

JavaScript 語(yǔ)法樹(shù)與代碼轉(zhuǎn)化

 

瀏覽器的兼容性問(wèn)題一直是前端項(xiàng)目開(kāi)發(fā)中的難點(diǎn)之一,往往客戶(hù)端瀏覽器的升級(jí)無(wú)法與語(yǔ)法特性的迭代保持一致;因此我們需要使用大量的墊片(Polyfill),以保證現(xiàn)代語(yǔ)法編寫(xiě)而成的 JavaScript 順利運(yùn)行在生產(chǎn)環(huán)境下的瀏覽器中,從而在可用性與代碼的可維護(hù)性之間達(dá)成較好的平衡。而以 Babel 為代表的語(yǔ)法轉(zhuǎn)化工具能夠幫我們自動(dòng)將 ES6 等現(xiàn)代 JavaScript 代碼轉(zhuǎn)化為可以運(yùn)行在舊版本瀏覽器中的 ES5 或其他同等的實(shí)現(xiàn);實(shí)際上,Babel 不僅僅是語(yǔ)法解析器,其更是擁有豐富插件的平臺(tái),稍加擴(kuò)展即可被應(yīng)用在前端監(jiān)控埋點(diǎn)、錯(cuò)誤日志收集等場(chǎng)景中。筆者也利用 Babel 以及 Babylon 為 swagger-decorator 實(shí)現(xiàn)了 flowToDecorator 函數(shù),其能夠從 Flow 文件中自動(dòng)提取出類(lèi)型信息并為類(lèi)屬性添加合適的注解。

Babel

自 Babel 6 之后,核心的 babel-core 僅暴露了部分核心接口,并使用 Babylon 進(jìn)行語(yǔ)法樹(shù)構(gòu)建,即上圖中的 Parse 與 Generate 步驟;實(shí)際的轉(zhuǎn)化步驟則是由配置的插件(Plugin)完成。而所謂的 Preset 則是一系列插件的合集,譬如 babel-preset-es2015 的源代碼中就定義了一系列的插件:

  1. return { 
  2.    plugins: [ 
  3.      [transformES2015TemplateLiterals, { loose, spec }], 
  4.      transformES2015Literals, 
  5.      transformES2015FunctionName, 
  6.      [transformES201***rrowFunctions, { spec }], 
  7.      transformES2015BlockScopedFunctions, 
  8.      [transformES2015Classes, optsLoose], 
  9.      transformES2015ObjectSuper, 
  10.      ... 
  11.      modules === "commonjs" && [transformES2015ModulesCommonJS, optsLoose], 
  12.      modules === "systemjs" && [transformES2015ModulesSystemJS, optsLoose], 
  13.      modules === "amd" && [transformES2015ModulesAMD, optsLoose], 
  14.      modules === "umd" && [transformES2015ModulesUMD, optsLoose], 
  15.      [transformRegenerator, { async: false, asyncGenerators: false }] 
  16.    ].filter(Boolean) // filter out falsy values 
  17.  };  

Babel 能夠?qū)⑤斎氲?JavaScript 代碼根據(jù)不同的配置將代碼進(jìn)行適當(dāng)?shù)剞D(zhuǎn)化,其主要步驟分為解析(Parse)、轉(zhuǎn)化(Transform)與生成(Generate):

在解析步驟中,Babel 分別使用詞法分析(Lexical Analysis)與語(yǔ)法分析(Syntactic Analysis)來(lái)將輸入的代碼轉(zhuǎn)化為抽象語(yǔ)法樹(shù);其中詞法分析步驟會(huì)將代碼轉(zhuǎn)化為令牌流,而語(yǔ)法分析步驟則是將令牌流轉(zhuǎn)化為語(yǔ)言?xún)?nèi)置的 AST 表示。

在轉(zhuǎn)化步驟中,Babel 會(huì)遍歷上一步生成的令牌流,根據(jù)配置對(duì)節(jié)點(diǎn)進(jìn)行添加、更新與移除等操作;Babel 本身并沒(méi)有進(jìn)行轉(zhuǎn)化操作,而是依賴(lài)于外置的插件進(jìn)行實(shí)際的轉(zhuǎn)化。

***的代碼生成則是將上一步中經(jīng)過(guò)轉(zhuǎn)化的抽象語(yǔ)法樹(shù)重新生成為代碼,并且同時(shí)創(chuàng)建 SourceMap;代碼生成相較于前兩步會(huì)簡(jiǎn)單很多,其核心思想在于深度優(yōu)先遍歷抽象語(yǔ)法樹(shù),然后生成對(duì)應(yīng)的代碼字符串。

抽象語(yǔ)法樹(shù)

抽象語(yǔ)法樹(shù)(Abstract Syntax Tree, AST)的作用在于牢牢抓住程序的脈絡(luò),從而方便編譯過(guò)程的后續(xù)環(huán)節(jié)(如代碼生成)對(duì)程序進(jìn)行解讀。AST 就是開(kāi)發(fā)者為語(yǔ)言量身定制的一套模型,基本上語(yǔ)言中的每種結(jié)構(gòu)都與一種 AST 對(duì)象相對(duì)應(yīng)。上文提及的解析步驟中的詞法分析步驟會(huì)將代碼轉(zhuǎn)化為所謂的令牌流,譬如對(duì)于代碼 n * n,其會(huì)被轉(zhuǎn)化為如下數(shù)組:

  1.   { type: { ... }, value: "n", start: 0, end: 1, loc: { ... } }, 
  2.   { type: { ... }, value: "*", start: 2, end: 3, loc: { ... } }, 
  3.   { type: { ... }, value: "n", start: 4, end: 5, loc: { ... } }, 
  4.   ... 
  5.  

其中每個(gè) type 是一系列描述該令牌屬性的集合:

  1.   type: { 
  2.     label: 'name'
  3.     keyword: undefined, 
  4.     beforeExpr: false
  5.     startsExpr: true
  6.     rightAssociative: false
  7.     isLoop: false
  8.     isAssign: false
  9.     prefix: false
  10.     postfix: false
  11.     binop: null
  12.     updateContext: null 
  13.   }, 
  14.   ... 
  15.  

這里的每一個(gè) type 類(lèi)似于 AST 中的節(jié)點(diǎn)都擁有 start、end、loc 等屬性;在實(shí)際應(yīng)用中,譬如對(duì)于 ES6 中的箭頭函數(shù),我們可以通過(guò) babylon 解釋器生成如下的 AST 表示:

  1.   type: { 
  2.     label: 'name'
  3.     keyword: undefined, 
  4.     beforeExpr: false
  5.     startsExpr: true
  6.     rightAssociative: false
  7.     isLoop: false
  8.     isAssign: false
  9.     prefix: false
  10.     postfix: false
  11.     binop: null
  12.     updateContext: null 
  13.   }, 
  14.   ... 
  15.  

我們可以使用 AST Explorer 這個(gè)工具進(jìn)行在線預(yù)覽與編輯;在上述的 AST 表示中,顧名思義,ArrowFunctionExpression 就表示該表達(dá)式為箭頭函數(shù)表達(dá)式。該函數(shù)擁有 foo 與 bar 這兩個(gè)參數(shù),參數(shù)所屬的 Identifiers 類(lèi)型是沒(méi)有任何子節(jié)點(diǎn)的變量名類(lèi)型;接下來(lái)我們發(fā)現(xiàn)加號(hào)運(yùn)算符被表示為了 BinaryExpression 類(lèi)型,并且其 operator 屬性設(shè)置為 +,而左右兩個(gè)參數(shù)分別掛載于 left 與 right 屬性下。在接下來(lái)的轉(zhuǎn)化步驟中,我們即是需要對(duì)這樣的抽象語(yǔ)法樹(shù)進(jìn)行轉(zhuǎn)換,該步驟主要由 Babel Preset 與 Plugin 控制;Babel 內(nèi)部提供了 babel-traverse 這個(gè)庫(kù)來(lái)輔助進(jìn)行 AST 遍歷,該庫(kù)還提供了一系列內(nèi)置的替換與操作接口。而經(jīng)過(guò)轉(zhuǎn)化之后的 AST 表示如下,在實(shí)際開(kāi)發(fā)中我們也常常首先對(duì)比轉(zhuǎn)化前后代碼的 AST 表示的不同,以了解應(yīng)該進(jìn)行怎樣的轉(zhuǎn)化操作:

  1. // AST shortened for clarity 
  2.     "program": { 
  3.         "type""Program"
  4.         "body": [ 
  5.             { 
  6.                 "type""ExpressionStatement"
  7.                 "expression": { 
  8.                     "type""Literal"
  9.                     "value""use strict" 
  10.                 } 
  11.             }, 
  12.             { 
  13.                 "type""ExpressionStatement"
  14.                 "expression": { 
  15.                     "type""FunctionExpression"
  16.                     "async"false
  17.                     "params": [ 
  18.                         { 
  19.                             "type""Identifier"
  20.                             "name""foo" 
  21.                         }, 
  22.                         { 
  23.                             "type""Identifier"
  24.                             "name""bar" 
  25.                         } 
  26.                     ], 
  27.                     "body": { 
  28.                         "type""BlockStatement"
  29.                         "body": [ 
  30.                             { 
  31.                                 "type""ReturnStatement"
  32.                                 "argument": { 
  33.                                     "type""BinaryExpression"
  34.                                     "left": { 
  35.                                         "type""Identifier"
  36.                                         "name""foo" 
  37.                                     }, 
  38.                                     "operator""+"
  39.                                     "right": { 
  40.                                         "type""Identifier"
  41.                                         "name""bar" 
  42.                                     } 
  43.                                 } 
  44.                             } 
  45.                         ] 
  46.                     }, 
  47.                     "parenthesizedExpression"true 
  48.                 } 
  49.             } 
  50.         ] 
  51.     } 
  52.  

自定義插件

Babel 支持以觀察者(Visitor)模式定義插件,我們可以在 visitor 中預(yù)設(shè)想要觀察的 Babel 結(jié)點(diǎn)類(lèi)型,然后進(jìn)行操作;譬如我們需要將下述箭頭函數(shù)源代碼轉(zhuǎn)化為 ES5 中的函數(shù)定義:

  1. // Source Code 
  2. const func = (foo, bar) => foo + bar; 
  3.  
  4. // Transformed Code 
  5. "use strict"
  6. const _func = function(_foo, _bar) { 
  7.   return _foo + _bar; 
  8. };  

在上一節(jié)中我們對(duì)比過(guò)轉(zhuǎn)化前后兩個(gè)函數(shù)語(yǔ)法樹(shù)的差異,這里我們就開(kāi)始定義轉(zhuǎn)化插件。首先每個(gè)插件都是以 babel 對(duì)象為輸入?yún)?shù),返回某個(gè)包含 visitor 的對(duì)象的函數(shù)。***我們需要調(diào)用 babel-core 提供的 transform 函數(shù)來(lái)注冊(cè)插件,并且指定需要轉(zhuǎn)化的源代碼或者源代碼文件:

  1. // plugin.js 文件,定義插件 
  2. import type NodePath from "babel-traverse"
  3.  
  4. export default function(babel) { 
  5.   const { types: t } = babel; 
  6.  
  7.   return { 
  8.     name"ast-transform", // not required 
  9.     visitor: { 
  10.       Identifier(path) { 
  11.         path.node.name = `_${path.node.name}`; 
  12.       }, 
  13.       ArrowFunctionExpression(path: NodePath<BabelNodeArrowFunctionExpression>, state: Object) { 
  14.         // In some conversion cases, it may have already been converted to a function while this callback 
  15.         // was queued up. 
  16.         if (!path.isArrowFunctionExpression()) return
  17.  
  18.         path.arrowFunctionToExpression({ 
  19.           // While other utils may be fine inserting other arrows to make more transforms possible, 
  20.           // the arrow transform itself absolutely cannot insert new arrow functions. 
  21.           allowInsertArrow: false
  22.           specCompliant: !!state.opts.spec 
  23.         }); 
  24.       } 
  25.     } 
  26.   }; 
  27.  
  28. // babel.js 使用插件 
  29. var babel = require('babel-core'); 
  30. var plugin= require('./plugin'); 
  31.  
  32. var out = babel.transform(src, { 
  33.   plugins: [plugin] 
  34. });  

常用轉(zhuǎn)化操作

遍歷

  • 獲取子節(jié)點(diǎn)路徑

我們可以通過(guò) path.node.{property} 的方式來(lái)訪問(wèn) AST 中節(jié)點(diǎn)屬性:

  1. // the BinaryExpression AST node has properties: `left`, `right`, `operator` 
  2. BinaryExpression(path) { 
  3.   path.node.left
  4.   path.node.right
  5.   path.node.operator; 
  6.  

我們也可以使用某個(gè)路徑對(duì)象的 get 方法,通過(guò)傳入子路徑的字符串表示來(lái)訪問(wèn)某個(gè)屬性:

  1. BinaryExpression(path) { 
  2.   path.get('left'); 
  3. Program(path) { 
  4.   path.get('body.0'); 
  5.  
  • 判斷某個(gè)節(jié)點(diǎn)是否為指定類(lèi)型

內(nèi)置的 type 對(duì)象提供了許多可以直接用來(lái)判斷節(jié)點(diǎn)類(lèi)型的工具函數(shù):

  1. BinaryExpression(path) { 
  2.   if (t.isIdentifier(path.node.left)) { 
  3.     // ... 
  4.   } 
  5.  

或者同時(shí)以淺比較來(lái)查看節(jié)點(diǎn)屬性:

  1. BinaryExpression(path) { 
  2.   if (t.isIdentifier(path.node.left, { name"n" })) { 
  3.     // ... 
  4.   } 
  5.  
  6. // 等價(jià)于 
  7. BinaryExpression(path) { 
  8.   if ( 
  9.     path.node.left != null && 
  10.     path.node.left.type === "Identifier" && 
  11.     path.node.left.name === "n" 
  12.   ) { 
  13.     // ... 
  14.   } 
  15.  
  • 判斷某個(gè)路徑對(duì)應(yīng)的節(jié)點(diǎn)是否為指定類(lèi)型
  1. BinaryExpression(path) { 
  2.   if (path.get('left').isIdentifier({ name"n" })) { 
  3.     // ... 
  4.   } 
  5.  
  • 獲取指定路徑的父節(jié)點(diǎn)

有時(shí)候我們需要從某個(gè)指定節(jié)點(diǎn)開(kāi)始向上遍歷獲取某個(gè)父節(jié)點(diǎn),此時(shí)我們可以通過(guò)傳入檢測(cè)的回調(diào)來(lái)判斷:

  1. path.findParent((path) => path.isObjectExpression()); 
  2.  
  3. // 獲取最近的函數(shù)聲明節(jié)點(diǎn) 
  4. path.getFunctionParent();  
  • 獲取兄弟路徑

如果某個(gè)路徑存在于 Function 或者 Program 中的類(lèi)似列表的結(jié)構(gòu)中,那么其可能會(huì)包含兄弟路徑:

  1. // 源代碼 
  2. var a = 1; // pathA, path.key = 0 
  3. var b = 2; // pathB, path.key = 1 
  4. var c = 3; // pathC, path.key = 2 
  5.  
  6. // 插件定義 
  7. export default function({ types: t }) { 
  8.   return { 
  9.     visitor: { 
  10.       VariableDeclaration(path) { 
  11.         // if the current path is pathA 
  12.         path.inList // true 
  13.         path.listKey // "body" 
  14.         path.key // 0 
  15.         path.getSibling(0) // pathA 
  16.         path.getSibling(path.key + 1) // pathB 
  17.         path.container // [pathA, pathB, pathC] 
  18.       } 
  19.     } 
  20.   }; 
  21.  
  • 停止遍歷

部分情況下插件需要停止遍歷,我們此時(shí)只需要在插件中添加 return 表達(dá)式:

  1. BinaryExpression(path) { 
  2.   if (path.node.operator !== '**'return
  3.  

我們也可以指定忽略遍歷某個(gè)子路徑:

  1. outerPath.traverse({ 
  2.   Function(innerPath) { 
  3.     innerPath.skip(); // if checking the children is irrelevant 
  4.   }, 
  5.   ReferencedIdentifier(innerPath, state) { 
  6.     state.iife = true
  7.     innerPath.stop(); // if you want to save some state and then stop traversal, or deopt 
  8.   } 
  9. });  

操作

  • 替換節(jié)點(diǎn)
  1. // 插件定義 
  2. BinaryExpression(path) { 
  3.   path.replaceWith( 
  4.     t.binaryExpression("**", path.node.left, t.numberLiteral(2)) 
  5.   ); 
  6.  
  7. // 代碼結(jié)果 
  8.   function square(n) { 
  9. -   return n * n; 
  10. +   return n ** 2; 
  11.   }  
  • 將某個(gè)節(jié)點(diǎn)替換為多個(gè)節(jié)點(diǎn)
  1. // 插件定義 
  2. ReturnStatement(path) { 
  3.   path.replaceWithMultiple([ 
  4.     t.expressionStatement(t.stringLiteral("Is this the real life?")), 
  5.     t.expressionStatement(t.stringLiteral("Is this just fantasy?")), 
  6.     t.expressionStatement(t.stringLiteral("(Enjoy singing the rest of the song in your head)")), 
  7.   ]); 
  8.  
  9. // 代碼結(jié)果 
  10.   function square(n) { 
  11. -   return n * n; 
  12. +   "Is this the real life?"
  13. +   "Is this just fantasy?"
  14. +   "(Enjoy singing the rest of the song in your head)"
  15.   }  
  • 將某個(gè)節(jié)點(diǎn)替換為源代碼字符串
  1. // 插件定義 
  2. FunctionDeclaration(path) { 
  3.   path.replaceWithSourceString(`function add(a, b) { 
  4.     return a + b; 
  5.   }`); 
  6.  
  7. // 代碼結(jié)果 
  8. function square(n) { 
  9. -   return n * n; 
  10. function add(a, b) { 
  11. +   return a + b; 
  12.   }  
  • 插入兄弟節(jié)點(diǎn)
  1. // 插件定義 
  2. FunctionDeclaration(path) { 
  3.   path.insertBefore(t.expressionStatement(t.stringLiteral("Because I'm easy come, easy go."))); 
  4.   path.insertAfter(t.expressionStatement(t.stringLiteral("A little high, little low."))); 
  5.  
  6. // 代碼結(jié)果 
  7. "Because I'm easy come, easy go."
  8.   function square(n) { 
  9.     return n * n; 
  10.   } 
  11. "A little high, little low." 
  • 移除某個(gè)節(jié)點(diǎn)
  1. // 插件定義 
  2. FunctionDeclaration(path) { 
  3.   path.remove(); 
  4.  
  5. // 代碼結(jié)果 
  6. function square(n) { 
  7. -   return n * n; 
  8. - }  
  • 替換節(jié)點(diǎn)
  1. // 插件定義 
  2. BinaryExpression(path) { 
  3.   path.parentPath.replaceWith( 
  4.     t.expressionStatement(t.stringLiteral("Anyway the wind blows, doesn't really matter to me, to me.")) 
  5.   ); 
  6.  
  7. // 代碼結(jié)果 
  8.   function square(n) { 
  9. -   return n * n; 
  10. +   "Anyway the wind blows, doesn't really matter to me, to me."
  11.   }  
  • 移除某個(gè)父節(jié)點(diǎn)
  1. // 插件定義 
  2. BinaryExpression(path) { 
  3.   path.parentPath.remove(); 
  4.  
  5. // 代碼結(jié)果 
  6.   function square(n) { 
  7. -   return n * n; 
  8.   }  

作用域

  • 判斷某個(gè)局部變量是否被綁定:
  1. FunctionDeclaration(path) { 
  2.   if (path.scope.hasBinding("n")) { 
  3.     // ... 
  4.   } 
  5.  
  6. FunctionDeclaration(path) { 
  7.   if (path.scope.hasOwnBinding("n")) { 
  8.     // ... 
  9.   } 
  10.  
  • 創(chuàng)建 UID
  1. FunctionDeclaration(path) { 
  2.   path.scope.generateUidIdentifier("uid"); 
  3.   // Node { type: "Identifier"name"_uid" } 
  4.   path.scope.generateUidIdentifier("uid"); 
  5.   // Node { type: "Identifier"name"_uid2" } 
  6.  
  • 將某個(gè)變量聲明提取到副作用中
  1. // 插件定義 
  2. FunctionDeclaration(path) { 
  3.   const id = path.scope.generateUidIdentifierBasedOnNode(path.node.id); 
  4.   path.remove(); 
  5.   path.scope.parent.push({ id, init: path.node }); 
  6.  
  7. // 代碼結(jié)果 
  8. function square(n) { 
  9. + var _square = function square(n) { 
  10.     return n * n; 
  11. - } 
  12. + };  
責(zé)任編輯:龐桂玉 來(lái)源: segmentfault
相關(guān)推薦

2017-07-24 09:45:15

JavaScript語(yǔ)法代碼

2018-04-09 14:26:06

Go語(yǔ)法實(shí)踐

2023-10-10 10:57:12

JavaScript代碼優(yōu)化

2017-01-20 09:45:20

JavaScript代碼質(zhì)量

2021-05-26 08:50:37

JavaScript代碼重構(gòu)函數(shù)

2016-06-20 11:32:27

JS原型class

2017-02-06 09:20:23

JavaScript實(shí)踐

2018-06-27 11:36:43

陳國(guó)興

2019-07-10 10:00:42

PHPPython語(yǔ)法

2010-05-27 17:35:36

MYSQL DELET

2024-10-16 13:47:40

2012-05-22 01:20:14

SyntaxHighlJavaScriptJava

2009-07-06 16:01:52

ASP與JSPJSP功能

2013-12-04 14:19:40

JavaScript代碼重用

2024-11-14 08:35:50

JavaScript管道操作符

2021-02-21 16:21:19

JavaScript閉包前端

2010-05-18 18:01:27

MySQLunion

2020-02-25 20:55:20

JavaScript開(kāi)發(fā) 技巧

2010-09-30 15:19:33

2021-04-01 17:04:34

Javascript語(yǔ)法數(shù)組
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)