日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
JavaScript語法樹與代碼轉(zhuǎn)化

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

成都創(chuàng)新互聯(lián)是專業(yè)的和龍網(wǎng)站建設(shè)公司,和龍接單;提供成都做網(wǎng)站、成都網(wǎng)站設(shè)計、成都外貿(mào)網(wǎng)站建設(shè),網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行和龍網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!

JavaScript 語法樹與代碼轉(zhuǎn)化

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

Babel

自 Babel 6 之后,核心的 babel-core 僅暴露了部分核心接口,并使用 Babylon 進(jìn)行語法樹構(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)與語法分析(Syntactic Analysis)來將輸入的代碼轉(zhuǎn)化為抽象語法樹;其中詞法分析步驟會將代碼轉(zhuǎn)化為令牌流,而語法分析步驟則是將令牌流轉(zhuǎn)化為語言內(nèi)置的 AST 表示。
  • 在轉(zhuǎn)化步驟中,Babel 會遍歷上一步生成的令牌流,根據(jù)配置對節(jié)點(diǎn)進(jìn)行添加、更新與移除等操作;Babel 本身并沒有進(jìn)行轉(zhuǎn)化操作,而是依賴于外置的插件進(jìn)行實(shí)際的轉(zhuǎn)化。
  • ***的代碼生成則是將上一步中經(jīng)過轉(zhuǎn)化的抽象語法樹重新生成為代碼,并且同時創(chuàng)建 SourceMap;代碼生成相較于前兩步會簡單很多,其核心思想在于深度優(yōu)先遍歷抽象語法樹,然后生成對應(yīng)的代碼字符串。

抽象語法樹

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

其中每個 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.   ... 

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

 
 
 
 
  1. // 源代碼 
  2. (foo, bar) => foo + bar; 
  3.  
  4. // 簡化的 AST 表示 
  5.     "program": { 
  6.         "body": [ 
  7.             { 
  8.                 "type": "ExpressionStatement", 
  9.                 "expression": { 
  10.                     "type": "ArrowFunctionExpression", 
  11.                     "params": [ 
  12.                         { 
  13.                             "type": "Identifier", 
  14.                             "name": "foo" 
  15.                         }, 
  16.                         { 
  17.                             "type": "Identifier", 
  18.                             "name": "bar" 
  19.                         } 
  20.                     ], 
  21.                     "body": { 
  22.                         "type": "BinaryExpression", 
  23.                         "left": { 
  24.                             "type": "Identifier", 
  25.                             "name": "foo" 
  26.                         }, 
  27.                         "operator": "+", 
  28.                         "right": { 
  29.                             "type": "Identifier", 
  30.                             "name": "bar" 
  31.                         } 
  32.                     } 
  33.                 } 
  34.             } 
  35.         ] 
  36.     } 

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

自定義插件

Babel 支持以觀察者(Visitor)模式定義插件,我們可以在 visitor 中預(yù)設(shè)想要觀察的 Babel 結(jié)點(diǎn)類型,然后進(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é)中我們對比過轉(zhuǎn)化前后兩個函數(shù)語法樹的差異,這里我們就開始定義轉(zhuǎn)化插件。首先每個插件都是以 babel 對象為輸入?yún)?shù),返回某個包含 visitor 的對象的函數(shù)。***我們需要調(diào)用 babel-core 提供的 transform 函數(shù)來注冊插件,并且指定需要轉(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, 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)路徑

我們可以通過 path.node.{property} 的方式來訪問 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; 

我們也可以使用某個路徑對象的 get 方法,通過傳入子路徑的字符串表示來訪問某個屬性:

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

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

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

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

 
 
 
 
  1. BinaryExpression(path) { 
  2.   if (t.isIdentifier(path.node.left, { name: "n" })) { 
  3.     // ... 
  4.   } 
  5.  
  6. // 等價于 
  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.   } 
  • 判斷某個路徑對應(yīng)的節(jié)點(diǎn)是否為指定類型
 
 
 
 
  1. BinaryExpression(path) { 
  2.   if (path.get('left').isIdentifier({ name: "n" })) { 
  3.     // ... 
  4.   } 

獲取指定路徑的父節(jié)點(diǎn)有時候我們需要從某個指定節(jié)點(diǎn)開始向上遍歷獲取某個父節(jié)點(diǎn),此時我們可以通過傳入檢測的回調(diào)來判斷:

 
 
 
 
  1. path.findParent((path) => path.isObjectExpression()); 
  2.  
  3. // 獲取最近的函數(shù)聲明節(jié)點(diǎn) 
  4. path.getFunctionParent(); 
  • 獲取兄弟路徑如果某個路徑存在于 Function 或者 Program 中的類似列表的結(jié)構(gòu)中,那么其可能會包含兄弟路徑:
 
 
 
 
  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.   }; 
  • 停止遍歷部分情況下插件需要停止遍歷,我們此時只需要在插件中添加 return 表達(dá)式:
 
 
 
 
  1. BinaryExpression(path) { 
  2.   if (path.node.operator !== '**') return; 

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

 
 
 
 
  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.   } 
  • 將某個節(jié)點(diǎn)替換為多個節(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.   } 
  • 將某個節(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."; 
  • 移除某個節(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.   } 
  • 移除某個父節(jié)點(diǎn)
 
 
 
 
  1. // 插件定義 
  2. BinaryExpression(path) { 
  3.   path.parentPath.remove(); 
  4.  
  5. // 代碼結(jié)果 
  6.   function square(n) { 
  7. -   return n * n; 
  8.   } 

作用域

  • 判斷某個局部變量是否被綁定:
 
 
 
 
  1. FunctionDeclaration(path) { 
  2.   if (path.scope.hasBinding("n")) { 
  3.     // ... 
  4.   } 
  5.  
  6. FunctionDeclaration(path) { 
  7.   if (path.scope.hasOwnBinding("n")) { 
  8.     // ... 
  9.   } 
  • 創(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" } 
  • 將某個變量聲明提取到副作用中
 
 
 
 
  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. + }; 

【本文是專欄作者“張梓雄 ”的原創(chuàng)文章,如需轉(zhuǎn)載請通過與作者聯(lián)系】

戳這里,看該作者更多好文


分享名稱:JavaScript語法樹與代碼轉(zhuǎn)化
當(dāng)前路徑:http://www.5511xx.com/article/cogcsoj.html