## 1.抽象語法樹(Abstract Syntax Tree)
`webpack`和`Lint`等很多的工具和庫的核心都是通過`Abstract Syntax Tree`抽象語法樹這個概念來實現對代碼的檢查、分析等操作的
* 通過了解抽象語法樹這個概念,你也可以隨手編寫類似的工具
## 2.抽象語法樹用途
* 代碼語法的檢查、代碼風格的檢查、代碼的格式化、代碼的高亮、代碼錯誤提示、代碼自動補全等等
* 如JSLint、JSHint對代碼錯誤或風格的檢查,發現一些潛在的錯誤
* IDE的錯誤提示、格式化、高亮、自動補全等等
* 代碼混淆壓縮
* UglifyJS2等
* 優化變更代碼,改變代碼結構使達到想要的結構
* 代碼打包工具webpack、rollup等等
* CommonJS、AMD、CMD、UMD等代碼規范之間的轉化
* CoffeeScript、TypeScript、JSX等轉化為原生Javascript
## 3.抽象語法樹定義
這些工具的原理都是通過`JavaScript Parser`把代碼轉化為一顆抽象語法樹(AST),這顆樹定義了代碼的結構,通過操縱這顆樹,我們可以精準的定位到聲明語句、賦值語句、運算語句等等,實現對代碼的分析、優化、變更等操作
> 在計算機科學中,抽象語法樹(abstract syntax tree或者縮寫為AST),或者語法樹(syntax tree),是源代碼的抽象語法結構的樹狀表現形式,這里特指編程語言的源代碼。
> Javascript的語法是為了給開發者更好的編程而設計的,但是不適合程序的理解。所以需要轉化為AST來使之更適合程序分析,瀏覽器編譯器一般會把源碼轉化為AST來進行進一步的分析等其他操作。

## 4.JavaScript Parser
* JavaScript Parser,把js源碼轉化為抽象語法樹的解析器。
* 瀏覽器會把js源碼通過解析器轉為抽象語法樹,再進一步轉化為字節碼或直接生成機器碼。
* 一般來說每個js引擎都會有自己的抽象語法樹格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了詳細SpiderMonkey AST format的詳細說明,算是業界的標準。
### 4.1 常用的JavaScript Parser
* esprima
* traceur
* acorn
* shift
### 4.2 esprima
* 通過[esprima](https://www.npmjs.com/package/esprima)把源碼轉化為AST
* 通過[estraverse](https://www.npmjs.com/package/estraverse)遍歷并更新AST
* 通過[escodegen](https://www.npmjs.com/package/escodegen)將AST重新生成源碼
* [astexplorer](https://astexplorer.net/)AST的可視化工具
~~~
mkdir zhufengast
cd zhufengast
cnpm i esprima estraverse escodegen- S
~~~
~~~
let esprima = require('esprima');
var estraverse = require('estraverse');
var escodegen = require("escodegen");
let code = 'function ast(){}';
let ast=esprima.parse(code);
let indent=0;
function pad() {
return ' '.repeat(indent);
}
estraverse.traverse(ast,{
enter(node) {
console.log(pad()+node.type);
if(node.type == 'FunctionDeclaration'){
node.id.name = 'ast_rename';
}
indent+=2;
},
leave(node) {
indent-=2;
console.log(pad()+node.type);
}
});
let generated = escodegen.generate(ast);
console.log(generated);
~~~
~~~
Program
FunctionDeclaration
Identifier
Identifier
BlockStatement
BlockStatement
FunctionDeclaration
Program
~~~
~~~
let esprima = require('esprima');//源代碼轉成AST語法樹
let estraverse = require('estraverse');//遍歷語法樹
let escodegen = require('escodegen');//把AST語法樹重新生成代碼的工具
let sourceCode = 'function ast(){}';
let ast = esprima.parse(sourceCode);//源代碼轉成AST語法樹
let indent =0;
function pad(){
return " ".repeat(indent);
}
estraverse.traverse(ast,{
enter(node){
console.log(pad()+node.type);
indent+=2;
},
leave(node){
indent-=2;
console.log(pad()+node.type);
}
});
~~~
## 5.babel插件
* 訪問者模式Visitor 對于某個對象或者一組對象,不同的訪問者,產生的結果不同,執行操作也不同
* [@babel/core](https://www.npmjs.com/package/@babel/core)Babel 的編譯器,核心 API 都在這里面,比如常見的 transform、parse
* [babylon](http://www.zhufengpeixun.cn/2020/html/26.webpack-5-AST.html)Babel 的解析器
* [babel-types](https://github.com/babel/babel/tree/master/packages/babel-types)用于 AST 節點的 Lodash 式工具庫, 它包含了構造、驗證以及變換 AST 節點的方法,對編寫處理 AST 邏輯非常有用
* [babel-traverse](https://www.npmjs.com/package/babel-traverse)用于對 AST 的遍歷,維護了整棵樹的狀態,并且負責替換、移除和添加節點
* [babel-types-api](https://babeljs.io/docs/en/next/babel-types.html)
* [Babel 插件手冊](https://github.com/brigand/babel-plugin-handbook/blob/master/translations/zh-Hans/README.md#asts)
* [babeljs.io](https://babeljs.io/en/repl.html)babel可視化編譯器
### 5.1 轉換箭頭函數
* [babel-plugin-transform-es2015-arrow-functions](https://www.npmjs.com/package/babel-plugin-transform-es2015-arrow-functions)
轉換前
~~~
const sum = (a,b)=>a+b
~~~

轉換后
~~~
var sum = function sum(a, b) {
return a + b;
};
~~~

~~~
npm i @babel/core babel-types -D
~~~
實現
~~~
let babel = require('@babel/core');
let t = require('babel-types');
const code = `const sum = (a,b)=>a+b`;
let transformArrowFunctions = {
visitor: {
ArrowFunctionExpression: (path) => {
let node = path.node;
let id = path.parent.id;
let params = node.params;
let body=t.blockStatement([
t.returnStatement(node.body)
]);
let functionExpression = t.functionExpression(id,params,body,false,false);
path.replaceWith(functionExpression);
}
}
}
const result = babel.transform(code, {
plugins: [transformArrowFunctions]
});
console.log(result.code);
~~~
### 5.2. 預計算babel插件
* path.parentPath 父路徑
轉換前
~~~
const result = 1 + 2;
~~~

轉換后
~~~
const result = 3;
~~~

```
let babel = require('@babel/core'); let t=require('babel-types'); let preCalculator={ visitor: { BinaryExpression(path) { let node=path.node; let left=node.left; let operator=node.operator; let right=node.right; if (!isNaN(left.value) && !isNaN(right.value)) { let result=eval(left.value+operator+right.value); path.replaceWith(t.numericLiteral(result)); if (path.parent&& path.parent.type == 'BinaryExpression') { preCalculator.visitor.BinaryExpression.call(null,path.parentPath); } } } } } const result = babel.transform('const sum = 1+2+3',{ plugins:\[ preCalculator \] }); console.log(result.code);
```
## 9\. AST
### 9.1 解析過程
AST整個解析過程分為兩個步驟
* 分詞:將整個代碼字符串分割成語法單元數組
* 語法分析:建立分析語法單元之間的關系
### 9.2 語法單元
Javascript 代碼中的語法單元主要包括以下這么幾種
* 關鍵字:`const`、`let`、`var`等
* 標識符:可能是一個變量,也可能是 if、else 這些關鍵字,又或者是 true、false 這些常量
* 運算符
* 數字
* 空格
* 注釋
### 9.3 詞法分析
~~~
let jsx = `let element=<h1>hello</h1>`;
function lexical(code) {
const tokens=[];
for (let i=0;i<code.length;i++){
let char=code.charAt(i);
if (char == '=') {
tokens.push({
type: 'operator',
value:char
});
}
if (char=='<') {
const token={
type: 'JSXElement',
value:char
}
tokens.push(token);
let isClose = false;
for (i++;i<code.length;i++){
char=code.charAt(i);
token.value+=char;
if (char=='>') {
if (isClose) {
break;
} else {
isClose=true;
}
}
}
continue;
}
if (/[a-zA-Z\$\_]/.test(char)) {
const token={
type: 'Identifier',
value:char
}
tokens.push(token);
for (i++;i<code.length;i++){
char=code.charAt(i);
if (/[a-zA-Z\$\_]/.test(char)) {
token.value+=char;
} else {
i--;
break;
}
}
continue;
}
if (/\s/.test(char)) {
const token={
type: 'whitespace',
value:char
}
tokens.push(token);
for (i++;i<code.length;i++){
char=code.charAt[i];
if (/\s/.test(char)) {
token.value+=char;
} else {
i--;
break;
}
}
continue;
}
}
return tokens;
}
let result=lexical(jsx);
console.log(result);
~~~
~~~
[
{ type: 'Identifier', value: 'let' },
{ type: 'whitespace', value: ' ' },
{ type: 'Identifier', value: 'element' },
{ type: 'operator', value: '=' },
{ type: 'JSXElement', value: '<h1>hello</h1>' }
]
~~~
### 9.4 語法分析
* 語義分析則是將得到的詞匯進行一個立體的組合,確定詞語之間的關系
* 簡單來說語法分析是對語句和表達式識別,這是個遞歸過程
~~~
// babylon7 https://astexplorer.net/
// babylon7 https://astexplorer.net/
function parse(tokens) {
const ast={
type: 'Program',
body: [],
sourceType:'script'
}
let i=0;//標示當前位置
let currentToken;//當前的符號
while ((currentToken = tokens[i])) {
if (currentToken.type == 'Identifier' && (currentToken.value == 'let'||currentToken.value == 'var')) {
const VariableDeclaration={
type: 'VariableDeclaration',
declarations:[]
}
i+=2;
currentToken=tokens[i];
let VariableDeclarator = {
type: 'VariableDeclarator',
id: {
type: 'Identifier',
name:currentToken.value
}
};
VariableDeclaration.declarations.push(VariableDeclarator);
i+=2;
currentToken=tokens[i];
if (currentToken.type=='JSXElement') {
let value=currentToken.value;
let [,type,children]=value.match(/([^<]+?)>([^<]+)<\/\1>/);
VariableDeclarator.init={
type: 'JSXElement',
openingElement:{
type:'JSXOpeningElement',
name:{
type:'JSXIdentifier',
name:'h1'
}
},
closingElement:{
type:'JSXClosingElement',
name:{
type:'JSXIdentifier',
name:'h1'
}
},
name: type,
children:[
{
type:'JSXText',
value:'hello'
}
]
}
} else {
VariableDeclarator.init={
type: 'Literal',
value:currentToken.value
}
}
ast.body.push(VariableDeclaration);
}
i++;
}
return ast;
}
let tokens=[
{type: 'Identifier',value: 'let'},
{type: 'whitespace',value: ' '},
{type: 'Identifier',value: 'element'},
{type: 'operator',value: '='},
{type: 'JSXElement',value: '<h1>hello</h1>'}
];
let result = parse(tokens);
console.log(result);
console.log(JSON.stringify(result));
~~~
~~~
{
"type": "Program",
"body": [{
"type": "VariableDeclaration",
"declarations": [{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "element"
},
"init": {
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": {
"type": "JSXIdentifier",
"name": "h1"
}
},
"closingElement": {
"type": "JSXClosingElement",
"name": {
"type": "JSXIdentifier",
"name": "h1"
}
},
"name": "h1",
"children": [{
"type": "JSXText",
"value": "hello"
}]
}
}]
}],
"sourceType": "script"
}
~~~
```
letutil\=require('util');
functionparse(tokens){
letast\=?{?type:'Program',body:\[\],sourceType:'module'};
leti\=0;//當前的索引
letcurrToken;//當前的token
while(currToken\=tokens\[i\]){
//第一次的時候?currToken?=?{?type:?'Keyword',?value:?'let'?}
if(currToken.type\=='Keyword'&&currToken.value\=='let'){
letVariableDeclaration\=?{type:'VariableDeclaration',declarations:\[\]};
ast.body.push(VariableDeclaration);
i+=2;//i=2
currToken\=tokens\[i\];//{?type:?'Identifier',?value:?'element'?},
letvariableDeclarator\=?{
type:'VariableDeclarator',
id:{type:'Identifier',name:currToken.value}
???????????}
VariableDeclaration.declarations.push(variableDeclarator);
i+=2;//i=4
currToken\=tokens\[i\];//?{?type:?'String',?value:?'hello'?}
if(currToken.type\=='String'){
variableDeclarator.init\=?{type:'StringLiteral',value:currToken.value};
??????????}elseif(currToken.type\=='JSXElement'){
letvalue\=currToken.value;
//type=h1?children=hello
let?\[,type,children\]?\=value.match(/\]+?)>(\[^/);??//hello
variableDeclarator.init\=?{
type:'JSXElement',//類型JSX元素
openingElement:{
type:'OpeningElement',
name:{type:'JSXIdentifier',name:type}
?????????????????},
closingElement:{
type:'ClosingElement',
name:{type:'JSXIdentifier',name:type}
?????????????????},
children:\[
?????????????????????{type:'JSXText',value:children}
?????????????????\]
?????????????}
??????????}
???????}
i++;
???}
returnast;
}
lettokens\=?\[
??{?type:?'Keyword',?value:?'let'?},
??{?type:?'WhiteSpace',?value:?'?'?},
??{?type:?'Identifier',?value:?'element'?},
??{?type:?'Equal',?value:?'='?},
???{type:"JSXElement",value:'hello'}
\];
//{?type:?'JSXElement',?value:?'hello'?}
letast\=parse(tokens);
ast.body\[0\].declarations\[0\].init\=??{
"type":?"ExpressionStatement",
"expression":?{
"type":?"CallExpression",
"callee":?{
"type":?"MemberExpression",
"computed":?false,
"object":?{
"type":?"Identifier",
"name":?"React",
??????????},
"property":?{
"type":?"Identifier",
"name":?"createElement",
??????????}
????????},
"arguments":?\[
??????????{
"type":?"Literal",
"value":?"h1",
"raw":?"\\"h1\\""
??????????},
??????????{
"type":?"Literal",
"value":?null,
"raw":?"null"
??????????},
??????????{
"type":?"Literal",
"value":?"hello",
"raw":?"\\"hello\\""
??????????}
????????\]
??????}
????}
console.log(JSON.stringify(ast))
```
- 文檔簡介
- 基礎面試題【珠峰2019.8】
- P01_call,aplly區別
- P02_綜合面試題講解2-2
- P03_箭頭函數和普通函數區別-綜合面試題講解2-3
- P05_實現indexOf
- P06_綜合面試題講解2-6
- P07_URL解析題
- P08_原型題
- P09_圖片延時加載
- P10_正則-包含數字字母下劃線
- P11_綜合面試題講解2-11
- P12_英文字母加空格
- P13_數組扁平化并去重
- P14_模擬實現new
- P15_合并數組
- P16_定時器,打印012345
- P17_匿名函數輸出值問題
- P18_a在什么情況下打印輸出+1+1+1
- P19_對數組的理解
- P20_冒泡排序
- P21_插入排序
- P22_快速排序
- P23_銷售額存在對象中
- P24_求數組的交集
- P25_旋轉數組
- P26_ [函數柯理化思想]
- P27_ [柯理化函數的遞歸]
- 網絡協議【珠峰2019.6】
- TypeScript+Axios入門+實戰【珠峰2019.11】
- 1.數據結構
- 2.函數和繼承
- 3.裝飾器
- 4.抽象類-接口-泛型
- 05-結構類型系統和類型保護
- 06-類型變換
- AST-抽象語法樹
- React性能優化【珠峰2019.10】
- 1-react性能優化
- 2-react性能優化
- 3.react-immutable
- React Hooks【珠峰2019.12】
- 前端框架及項目面試
- 第07章 React 使用
- 7-1 React使用-考點串講
- 7-2 JSX基本知識點串講
- 7-3 JSX如何判斷條件和渲染列表
- 7-4 React事件為何bind this
- 7-5 React事件和DOM事件的區別
- 7-6 React表單知識點串講
- 7-7 React父子組件通訊
- 7-8 setState為何使用不可變值
- 7-9 setState是同步還是異步
- 7-10 setState合適會合并state
- 7-11 React組件生命周期
- 7-12 React基本使用-知識點總結和復習
- 7-13 React函數組件和class組件有何區別
- 7-14 什么是React非受控組件
- 7-15 什么場景需要用React Portals
- 7-16 是否用過React Context
- 7-17 React如何異步加載組件
- 7-18 React性能優化-SCU的核心問題在哪里
- 7-19 React性能優化-SCU默認返回什么
- 7-20 React性能優化-SCU一定要配合不可變值
- 7-21 React性能優化-PureComponent和memo
- 7-22 React性能優化-了解immutable.js
- 7-23 什么是React高階組件
- 7-24 什么是React Render Props
- 7-25 React高級特性考點總結
- 7-26 Redux考點串講
- 7-27 描述Redux單項數據流
- 7-28 串講react-redux知識點
- 7-29 Redux action如何處理異步
- 7-30 簡述Redux中間件原理
- 7-31 串講react-router知識點
- 7-32 React使用-考點總結
- 第08章 React 原理
- 8-1 React原理-考點串講
- 8-2 再次回顧不可變值
- 8-3 vdom和diff是實現React的核心技術
- 8-4 JSX本質是什么
- 8-5 說一下React的合成事件機制
- 8-6 說一下React的batchUpdate機制
- 8-7 簡述React事務機制
- 8-8 說一下React組件渲染和更新的過程
- 8-9 React-fiber如何優化性能
- 第09章 React 面試真題演練
- 9-1 React真題演練-1-組件之間如何通訊
- 9-2 React真題演練-2-ajax應該放在哪個生命周期
- 9-3 React真題演練-3-組件公共邏輯如何抽離
- 9-4 React真題演練-4-React常見性能優化方式
- 9-5 React真題演練-5-React和Vue的區別
- 第10章 webpack 和 babel
- 10-1 webpack考點梳理
- 10-2 webpack基本配置串講(上)
- 10-3 webpack基本配置串講(下)
- 10-4 webpack如何配置多入口
- 10-5 webpack如何抽離壓縮css文件
- 10-6 webpack如何抽離公共代碼和第三方代碼
- 10-7 webpack如何實現異步加載JS
- 10-8 module chunk bundle 的區別
- 10-9 webpack優化構建速度-知識點串講
- 10-11 happyPack是什么
- 10-12 webpack如何配置熱更新
- 10-13 何時使用DllPlugin
- 10-14 webpack優化構建速度-考點總結和復習
- 10-15 webpack優化產出代碼-考點串講
- 10-16 什么是Tree-Shaking
- 10-17 ES Module 和 Commonjs 的區別
- 10-18 什么是Scope Hostin
- 10-19 babel基本概念串講
- 10-20 babel-polyfill是什么
- 10-21 babel-polyfill如何按需引入
- 10-22 babel-runtime是什么
- 10-23 webpack考點總結和復習
- 10-24 webpack面試真題-前端代碼為何要打包
- 10-25 webpack面試真題-為何Proxy不能被Polyfill
- 10-26 webpack面試真題-常見性能優化方法