成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

JavaScript 語法樹與代碼轉化

開發 開發工具
本文引用的參考資料聲明于 JavaScript 學習與實踐資料索引中,特別需要聲明是部分代碼片引用自 Babel Handbook 開源手冊;也歡迎關注前端每周清單系列獲得一手資訊。

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

JavaScript 語法樹與代碼轉化

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

Babel

自 Babel 6 之后,核心的 babel-core 僅暴露了部分核心接口,并使用 Babylon 進行語法樹構建,即上圖中的 Parse 與 Generate 步驟;實際的轉化步驟則是由配置的插件(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 能夠將輸入的 JavaScript 代碼根據不同的配置將代碼進行適當地轉化,其主要步驟分為解析(Parse)、轉化(Transform)與生成(Generate):

  • 在解析步驟中,Babel 分別使用詞法分析(Lexical Analysis)與語法分析(Syntactic Analysis)來將輸入的代碼轉化為抽象語法樹;其中詞法分析步驟會將代碼轉化為令牌流,而語法分析步驟則是將令牌流轉化為語言內置的 AST 表示。
  • 在轉化步驟中,Babel 會遍歷上一步生成的令牌流,根據配置對節點進行添加、更新與移除等操作;Babel 本身并沒有進行轉化操作,而是依賴于外置的插件進行實際的轉化。
  • ***的代碼生成則是將上一步中經過轉化的抽象語法樹重新生成為代碼,并且同時創建 SourceMap;代碼生成相較于前兩步會簡單很多,其核心思想在于深度優先遍歷抽象語法樹,然后生成對應的代碼字符串。

抽象語法樹

抽象語法樹(Abstract Syntax Tree, AST)的作用在于牢牢抓住程序的脈絡,從而方便編譯過程的后續環節(如代碼生成)對程序進行解讀。AST 就是開發者為語言量身定制的一套模型,基本上語言中的每種結構都與一種 AST 對象相對應。上文提及的解析步驟中的詞法分析步驟會將代碼轉化為所謂的令牌流,譬如對于代碼 n * n,其會被轉化為如下數組:

  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 中的節點都擁有 start、end、loc 等屬性;在實際應用中,譬如對于 ES6 中的箭頭函數,我們可以通過 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 這個工具進行在線預覽與編輯;在上述的 AST 表示中,顧名思義,ArrowFunctionExpression 就表示該表達式為箭頭函數表達式。該函數擁有 foo 與 bar 這兩個參數,參數所屬的 Identifiers 類型是沒有任何子節點的變量名類型;接下來我們發現加號運算符被表示為了 BinaryExpression 類型,并且其 operator 屬性設置為 +,而左右兩個參數分別掛載于 left 與 right 屬性下。在接下來的轉化步驟中,我們即是需要對這樣的抽象語法樹進行轉換,該步驟主要由 Babel Preset 與 Plugin 控制;Babel 內部提供了 babel-traverse 這個庫來輔助進行 AST 遍歷,該庫還提供了一系列內置的替換與操作接口。而經過轉化之后的 AST 表示如下,在實際開發中我們也常常首先對比轉化前后代碼的 AST 表示的不同,以了解應該進行怎樣的轉化操作:

  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 中預設想要觀察的 Babel 結點類型,然后進行操作;譬如我們需要將下述箭頭函數源代碼轉化為 ES5 中的函數定義:

  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. }; 

在上一節中我們對比過轉化前后兩個函數語法樹的差異,這里我們就開始定義轉化插件。首先每個插件都是以 babel 對象為輸入參數,返回某個包含 visitor 的對象的函數。***我們需要調用 babel-core 提供的 transform 函數來注冊插件,并且指定需要轉化的源代碼或者源代碼文件:

  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. }); 

常用轉化操作

遍歷

  • 獲取子節點路徑

我們可以通過 path.node.{property} 的方式來訪問 AST 中節點屬性:

  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'); 
  • 判斷某個節點是否為指定類型

1.內置的 type 對象提供了許多可以直接用來判斷節點類型的工具函數:

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

或者同時以淺比較來查看節點屬性:

  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.   } 
  • 判斷某個路徑對應的節點是否為指定類型
  1. BinaryExpression(path) { 
  2.   if (path.get('left').isIdentifier({ name"n" })) { 
  3.     // ... 
  4.   } 

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

  1. path.findParent((path) => path.isObjectExpression()); 
  2.  
  3. // 獲取最近的函數聲明節點 
  4. path.getFunctionParent(); 
  • 獲取兄弟路徑如果某個路徑存在于 Function 或者 Program 中的類似列表的結構中,那么其可能會包含兄弟路徑:
  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 表達式:
  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. }); 

操作

  • 替換節點
  1. // 插件定義 
  2. BinaryExpression(path) { 
  3.   path.replaceWith( 
  4.     t.binaryExpression("**", path.node.left, t.numberLiteral(2)) 
  5.   ); 
  6.  
  7. // 代碼結果 
  8.   function square(n) { 
  9. -   return n * n; 
  10. +   return n ** 2; 
  11.   } 
  • 將某個節點替換為多個節點
  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. // 代碼結果 
  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.   } 
  • 將某個節點替換為源代碼字符串
  1. // 插件定義 
  2. FunctionDeclaration(path) { 
  3.   path.replaceWithSourceString(`function add(a, b) { 
  4.     return a + b; 
  5.   }`); 
  6.  
  7. // 代碼結果 
  8. function square(n) { 
  9. -   return n * n; 
  10. function add(a, b) { 
  11. +   return a + b; 
  12.   } 
  • 插入兄弟節點
  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. // 代碼結果 
  7. "Because I'm easy come, easy go."
  8.   function square(n) { 
  9.     return n * n; 
  10.   } 
  11. "A little high, little low."
  • 移除某個節點
  1. // 插件定義 
  2. FunctionDeclaration(path) { 
  3.   path.remove(); 
  4.  
  5. // 代碼結果 
  6. function square(n) { 
  7. -   return n * n; 
  8. - } 
  • 替換節點
  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. // 代碼結果 
  8.   function square(n) { 
  9. -   return n * n; 
  10. +   "Anyway the wind blows, doesn't really matter to me, to me."
  11.   } 
  • 移除某個父節點
  1. // 插件定義 
  2. BinaryExpression(path) { 
  3.   path.parentPath.remove(); 
  4.  
  5. // 代碼結果 
  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.   } 
  • 創建 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. // 代碼結果 
  8. function square(n) { 
  9. + var _square = function square(n) { 
  10.     return n * n; 
  11. - } 
  12. + }; 

【本文是51CTO專欄作者“張梓雄 ”的原創文章,如需轉載請通過51CTO與作者聯系】

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

責任編輯:武曉燕 來源: 51CTO專欄
相關推薦

2017-07-26 17:38:10

JavaScriptBabel

2018-06-27 11:36:43

陳國興

2024-10-16 13:47:40

2019-07-10 10:00:42

PHPPython語法

2010-05-27 17:35:36

MYSQL DELET

2013-12-04 14:19:40

JavaScript代碼重用

2012-05-22 01:20:14

SyntaxHighlJavaScriptJava

2009-07-06 16:01:52

ASP與JSPJSP功能

2024-11-14 08:35:50

JavaScript管道操作符

2010-05-18 18:01:27

MySQLunion

2021-04-01 17:04:34

Javascript語法數組

2010-09-30 15:19:33

2024-08-09 10:15:34

2009-06-09 21:59:13

語法高亮Javascript

2010-03-18 16:51:00

python語法入門

2009-08-19 15:38:59

C#代碼

2010-10-09 10:10:55

JavaScriptFunction對象

2013-09-09 09:50:27

代碼語法工具

2010-09-10 14:12:07

JavaScript

2009-10-26 15:07:12

checkbox樹
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩一区二区在线视频 | 亚洲精品久久久久久久久久久久久 | 日韩精品av | 中文字幕一区二区三区乱码在线 | 自拍偷拍精品 | 黑人巨大精品欧美黑白配亚洲 | 中文字幕久久精品 | 国产一区 | 激情网站在线观看 | 夜夜夜夜草 | 久久999| 91久久国产综合久久 | 久久网国产 | 日韩欧美国产成人一区二区 | 一级毛片在线播放 | japanhdxxxx裸体| 国产99小视频| 精品一区二区三区中文字幕 | av日日操 | 暖暖成人免费视频 | 狠狠色狠狠色综合系列 | 亚洲高清av| 亚洲成人久久久 | 一区二区三区在线免费观看 | 98久久| 欧美综合国产精品久久丁香 | 东方伊人免费在线观看 | www日本在线 | 国产一区二区三区在线免费观看 | 久草网站 | 在线婷婷 | av香蕉| 精品国产欧美一区二区三区成人 | 欧美人成在线视频 | 国产精品有限公司 | 九九久久在线看 | 国产视频福利一区 | 国产91 在线播放 | 久久久免费少妇高潮毛片 | 国产精品99久久久久久www | 日韩在线免费电影 |