Babel节点分类及常见属性
当使用parse解析器解析一段代码时,会将字符串代码解析为一个ast节点树。该节点树由很多个节点组成。我们可以把众多的节点进行分类,根据节点生成的代码不同,我们可以把节点分为:Literal、Identifier、Statement、Declaration、Expression、Class、Modules、Program、Directive、File、Comment
这几大类。
这里需要知道例如stringLiteral和t.stringLiteral()的区别。stringLiteral是一个字符串字面量节点,而t.stringLiteral()是创建该节点的函数。该节点有很多属性用来描述一个字符串。
Literal (字面量)
以下是常见的字面量节点及其常见属性:
BooleanLiteral
布尔字面量节点。
js
布尔字面量节点,其常见属性如下:
{
type: 'BooleanLiteral',
value: true
}
t.booleanLiteral(true)
StringLiteral
字符串字面量节点
js
字符串字面量节点,其常见属性如下:
{
type: 'StringLiteral',
value: 'hunly'
}
t.stringLiteral('hunly')
NumericLiteral
数字字面量节点。
bash
数字字面量节点,其常见属性如下:
{
type: 'NumericLiteral',
value: 100
}
t.numericLiteral(100);
TemplateLiteral
模板字符串节点。该节点有两个属性,分别是quasis和expressions。quasis表示的是模板字面量的静态部分,它是一个数组,该数组中的元素为TemplateElement节点。expressions表示的是模板字符串动态部分,因为动态部分也可以有很多处,因此它也是一个数组,但是该数组中的元素节点可以是表达式节点、变量节点等节点。数组中元素是什么节点,取决于动态部分是什么js代码。
这里需要注意的是,babel在拼接TemplateLiteral节点对应的模板字符串代码时,对于静态和动态节点的处理是:首先先截取静态数组中的节点,然后截取动态节点中的节点,再截取静态节点,依次类推。
但是这里有一个规定:Number of TemplateLiteral quasis should be exactly one more than the number of expressions
。即quasis的数量应该比expressions数量多一个。如果js代码中的静态部分和动态部分数量恰好相同,那么可以在quasis数组的最后放一个值为空串的TemplateElement节点。
js
const tem = `age:${age},name:${name}`
其中`age:`和`,name:`为静态部分,一共有两处
而`${age}`和`${name}`为动态部分,一共有两处
js
模板字符串字面量,其常见属性如下:
{
type: 'TemplateLiteral',
quasis: [
{ type: 'TemplateElement', value: [Object], tail: false },
{ type: 'TemplateElement', value: [Object], tail: true }
],
expressions: [
{ type: 'Identifier', name: 'name' }
]
}
t.templateLiteral(quasis, expressions);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建模板字面量的静态部分数组
const quasis = [
t.templateElement({ raw: 'Hello, ', cooked: 'Hello, ' }), // 第一个静态部分
t.templateElement({ raw: '!', cooked: '!' }) // 第二个静态部分
];
// 创建模板字面量中的表达式数组
const expressions = [
t.identifier('name') // 表达式,可以是任何 AST 节点
];
// 使用 t.templateLiteral 方法创建模板字面量的 AST 节点
const templateLiteralNode = t.templateLiteral(quasis, expressions);
console.log(generator(templateLiteralNode).code)
// 该节点对应的js代码如下:
`Hello, ${name}!`
TemplateElement
模板字符串静态部分节点,该节点是模板字符串节点的子节点,和TemplateLiteral节点配置组成完成的模板字符串节点。该节点有两个常用属性:value和tail。value
:这是一个包含了模板字面量部分的值的对象,有两个属性raw
:表示原始的、未经转义的字符串值,cooked
:表示经过转义处理的字符串值,通常与 raw
属性相同。通常情况下这两个属性的属性值保持一致即可。tail
:这是一个布尔值,表示该部分是否是模板字面量的最后一个部分。如果该值为true
,则表示该节点是模板字符串的最后一个部分。
js
模板字符串静态部分节点,其常见属性如下:
{
type: 'TemplateElement',
value: { raw: 'Hello, ', cooked: 'Hello, ' },
tail: false
}
t.templateElement(value, tail);
js
// 创建一个静态部分的模板字面量部分
const staticPart = t.templateElement({ raw: 'Hello, ', cooked: 'Hello, ' }, false);
RegExpLiteral
正则字面量节点,通过该节点创建对应的正则表达式。需要注意的是该正则表达式是字面量形式,即/xxx/g
这种形式。该节点有两个属性,pattern表示正则表达式的模式匹配部分,即双斜杠之间的内容。flags这是一个字符串,表示正则表达式的标志部分,包括 g
(全局匹配)、i
(忽略大小写)、m
(多行匹配)等
js
正则字面量节点,其常见属性如下:
{
type: 'RegExpLiteral',
pattern: 'abc',
flags: 'g'
}
t.regExpLiteral(pattern, flags);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建一个表示正则表达式字面量的 AST 节点
const regExpLiteralNode = t.regExpLiteral('hello', 'i');
console.log(generator(regExpLiteralNode).code)
// 该节点对应的js代码如下:
/hello/i
NullLiteral
null字面量节点。
js
null字面量,其常见属性如下:
{ type: 'NullLiteral' }
t.nullLiteral();
Identifier (变量)
以下是常见的变量节点:
Identifier
变量节点,通过该节点可以创建一个js变量。该节点的name值就是对应的变量名。
js
变量节点,其常见属性如下:
{
type: 'Identifier',
name: 'object'
}
t.identifier(name);
js
const identifierNode = t.identifier('object') // 就会创建一个名为object的变量
Statement (语句)
语句是代码执行的最小单位,可以说,代码是由语句(Statement)构成的。
BlockStatement
块语句节点。在 Babel 中,t.blockStatement(body, directives)
方法用于创建一个表示代码块(Block Statement)的 AST 节点。代码块是一组语句的集合,通常用大括号 {}
包裹起来。该节点一般用于函数体或者类主体的创建,可以通过该节点创建对应的块代码。
该节点有两个属性body和directives。body是一个数组,里面是ExpressionStatement节点的集合,表示块代码中一行行的代码。directives是一个可选的数组,包含了代码块中的指令,例如 "use strict"
。
js
块语句节点,其常见属性如下:
{
type: 'BlockStatement',
body: [ ExpressionStatement,ExpressionStatement, .... ],
directives: []
}
t.blockStatement(body, directives);
js
// 创建一个表示代码块的 AST 节点
const blockStatementNode = t.blockStatement([
// 这里可以放入代码块中的语句
t.expressionStatement(t.identifier('console.log("Hello")')), // 示例语句:console.log("Hello")
]);
ExpressionStatement
表达式语句节点,该节点一般和BlockStatement节点配合使用。如果我们想要在一个代码块中添加一段代码,我们可以使用该节点将表达式节点进行封装,然后放到blockStatement的body中,相当于在代码块中添加了一行表达式语句。
js
表达式语句节点,其常见属性如下:
{
type: 'ExpressionStatement',
expression: ExpressionNode
}
t.expressionStatement(expression);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建一个表示表达式语句的 AST 节点
const expressionStatementNode = t.expressionStatement(
// 这里可以放入一个表达式的 AST 节点
t.callExpression(
t.identifier('console.log'), // 函数调用的标识符
[t.stringLiteral('Hello')] // 函数调用的参数列表,这里是一个字符串字面量
)
);
console.log(generator(expressionStatementNode).code)
// 该节点对应的js代码如下:
console.log("Hello");
Declaration (声明)
以下是常见的声明节点:
VariableDeclaration
声明节点,通过该节点来创建对应的声明语句。声明节点我们可以认为是含有赋值的定义操作,kind属性表示当前是用哪种方式定义变量,一般是const | var | let
等枚举值。declarations属性是一个数组,里面是VariableDeclaratorNode节点,这个节点才是真正能够声明变量的节点。该节点和VariableDeclarator节点的区别在于,该节点类似于一个容器,如果想要实现变量声明,需要用这个节点包裹。因为declarations是一个数组,因此里面可以放多个声明节点,因此可以批量声明。
js
声明节点,其常见属性如下:
{
type: 'VariableDeclaration',
kind: 'const',
declarations: [VariableDeclaratorNode]
}
t.variableDeclaration(kind, declarations);
variableDeclarator
这个节点是我们真正用来声明变量的节点,这个节点主要有id和Init两个属性,其中id代表变量名,一般是一个identifier变量节点,而init是一个初始化表达式,即给变量赋初值,可以是字面量、对象、函数、数组等,因此它的节点也是不固定的,因初值类型决定。
js
声明节点,其常见属性如下:
{
type: 'VariableDeclarator',
id: { type: 'Identifier', name: 'x' },
init: { type: 'NumericLiteral', value: 10, raw: '10' }
}
t.variableDeclarator(id, init)
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建变量声明节点:const myVar = 42;
const variableDeclaration = t.variableDeclaration(
'const', // 变量声明类型,可以是 'var', 'let', 或 'const'
[
t.variableDeclarator(
t.identifier('myVar'), // 变量名
t.numericLiteral(42) // 变量值
)
]
);
console.log(generator(variableDeclaration).code)
// 该节点对应的js代码如下:
const myVar = 42;
FunctionDeclaration
函数声明节点,通过该节点来创建对应的函数声明语句。函数的定义分为两种:函数声明、函数定义。前者是使用function关键字定义不赋值,后者是通过function定义并赋值给一个变量。例如:function add(){}
这种就是函数声明式。其中,id表示当前声明函数的函数名,一般是一个变量节点,pararms是函数参数,因为参数可以有多个,因此他是一个数组。该数组中一般是变量节点,毕竟是形参而不是实参。body表示函数体,一般是一个blockStatement节点,表示块级语句即函数的函数体。
js
函数声明节点,其常见属性如下:
{
type: 'FunctionDeclaration',
id: { type: 'Identifier', name: 'add' },
params: [IdentifierNode],
body: BlockStatementNode,
generator: false,
async: false
}
t.functionDeclaration(id, params, body, generator, async);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
const functionNode = t.functionDeclaration(
t.identifier('add'), // 函数名称
[
t.identifier('name')
], // 参数列表
t.blockStatement(
[
t.returnStatement(t.identifier('name'))
]
) // 函数体内容
);
console.log(generator(functionNode).code)
// 该节点对应的js代码如下:
function add(name) {
return name;
}
Expression (表达式)
以下是常用的表达式节点:
ArrayExpression
数组表达式节点。所谓的数组表达式是[1,2,3]
这种,而const arr = [1,2,3]
这种属于声明表达式,而赋值的后面部分[1,2,3]
才是一个数组表达式。这里的elements是一个数组,至于这里数组里面是什么类型的节点。其实本质上取决于你想在这个数组里面放什么,放函数就是一个函数声明节点,放数字就是数字节点、对象就是对象节点。
js
函数表达式节点,其常见属性如下:
{
type: 'ArrayExpression',
elements: [literalNode | ExpressionNode | 等]
}
t.arrayExpression(elements);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建一个表示数组表达式的 AST 节点
const arrayExpressionNode = t.arrayExpression([
t.numericLiteral(1), // 数字字面量 1
t.stringLiteral('hello'), // 字符串字面量 'hello'
t.booleanLiteral(true) // 布尔字面量 true
]);
console.log(generator(arrayExpressionNode).code)
// 该节点对应的js代码如下:
[1, "hello", true]
AssignmentExpression
赋值表达式节点。其中operator表示操作符,一般是'='
。left为赋值操作的左边节点,一般为一个变量节点。right是赋值操作的右边节点,一般是一个字面量或者表达式或者变量节点。
js
赋值表达式节点,其常见属性如下:
{
type: 'AssignmentExpression',
operator: '=',
left: IdentifierNode,
right: LiteralNode | IdentifierNode | 等
}
t.assignmentExpression(operator, left, right);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require("@babel/types");
// 创建左侧的标识符节点
const left = t.identifier("x");
// 创建右侧的表达式节点
const right = t.binaryExpression("+", t.identifier("y"), t.numericLiteral(1));
// 创建赋值表达式节点
const assignmentExpression = t.assignmentExpression("=", left, right);
console.log(generator(assignmentExpression).code)
// 该节点对应的js代码如下:
x = y + 1
BinaryExpression
二元表达式节点。其中operator为二元操作符,二元操作符有很多,这里以+
为例子。left为二元操作的左边节点,一般为字面量节点或者变量节点,right是二元操作的右边节点,一般是字面量节点或者变量节点。
js
二元表达式,其常见属性如下:
{
type: 'BinaryExpression',
operator: '+',
left: Node,
right: Node
}
t.binaryExpression(operator, left, right);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require("@babel/types");
// 创建左侧的表达式节点
const left = t.numericLiteral(1);
// 创建右侧的表达式节点
const right = t.numericLiteral(1);
// 创建二元表达式节点
const binaryExpression = t.binaryExpression("+", left, right);
console.log(generator(binaryExpression).code)
// 该节点对应的js代码如下:
1 + 1
UnaryExpression
一元表达式节点。其中operator表示一元操作符,常见的一元操作符包括:"void" | "throw" | "delete" | "!" | "+" | "-" | "~" | "typeof"
等,而argument是一个表达式,意思是作用于谁,一般是变量节点或者表达式节点。而prefix表示是否为前缀表达式,意思是一元操作符在变量的左边还是右边,一般默认为true即左边。
js
一元表达式节点,其常见属性如下:
{
type: 'UnaryExpression',
operator: '!',
argument: { type: 'Identifier', name: 'x' },
prefix: true
}
t.unaryExpression(operator, argument, prefix);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建一个表示一元表达式的 AST 节点
const unaryExpressionNode = t.unaryExpression(
'-', // 操作符为负号
t.identifier('x'), // 操作数为标识符 'x'
true // 前缀一元操作符
);
console.log(generator(unaryExpressionNode).code)
// 该节点对应的js代码如下:
-x
FunctionExpression
函数表达式节点,这里需要和functionDeclaration节点区别开来,前者是函数表达式,即const add = function(a){}
后者是函数声明,即function add(a){}
。一般这个节点和variableDeclarator节点配合组成函数表达式。id、params、body和functionDeclaration节点作用相同。
js
函数表达式表达式节点,其常见属性如下:
{
type: 'FunctionExpression',
id: null,
params: [ Identifier,Identifier,....],
body: BlockStatement,
generator: false,
async: false
}
t.functionExpression(id, params, body, false, false);
和variableDeclarator节点配置使用示例如下:
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
const addFunctionExpression = t.variableDeclaration(
'const', // 声明类型
[
t.variableDeclarator(
t.identifier('add'), // 变量名
t.functionExpression(
null, // 函数标识符(可以为 null 表示匿名函数)
[t.identifier('a')], // 参数列表
t.blockStatement([]) // 函数体
)
)
]
);
console.log(generator(addFunctionExpression).code)
// 该节点对应的js代码如下:
const add = function (a) {}
ArrowFunctionExpression
箭头函数表达式节点。params表示箭头函数的参数,因此参数可以有多个,因此该属性是一个数组,数组中的元素一般是变量节点。body表示箭头函数的函数体部分,和其他函数相关节点一样,可以是一个blockStatement节点,也可以是一个变量节点、字面量节点、表达式节点,因为如果我们只有返回语句,那么就可以简写为 () => xxx
的形式,而xxx
部分可以是变量、字面量等。async表示是否为异步函数。
js
箭头函数表达式节点,其常见属性如下:
{
type: "ArrowFunctionExpression",
params: [Identifier | Pattern | RestElement],
body: blockStatement | Identifier | Literal | ...,
async: false,
expression: null
}
t.arrowFunctionExpression(params, body, async);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建一个表示箭头函数表达式的 AST 节点
const arrowFunctionExpressionNode = t.arrowFunctionExpression(
[t.identifier('x'), t.identifier('y')], // 参数为标识符 'x'
t.binaryExpression(
'+', // 加法操作符
t.identifier('x'), // 左操作数为标识符 'x'
t.numericLiteral(1) // 右操作数为数字字面量 1
),
false // 非异步函数
);
console.log(generator(arrowFunctionExpressionNode).code)
// 该节点对应的js代码如下:
(x, y) => x + 1
ThisExpression
this表达式节点,这个表达式只有一个type属性表示该节点是一个this表达式,一般情况下它不会单独存在,它会和其他节点配置使用,例如和memberExpress节点配合组成this.xxx
形式。当然你可以单独使用它,例如fnc(this)
将this作为参数传入,则使用t.thisExpression()创建一个this节点。因为它使用的特殊性,因此该节点的生成函数没有任何参数。
js
this表达式节点,其常见属性如下:
{
type: "ThisExpression"
}
t.thisExpression();
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建 this 表达式节点
const thisExpr = t.thisExpression();
// 创建方法调用表达式节点:this.getName()
const callExpr = t.callExpression(
t.memberExpression(thisExpr, t.identifier('getName')),
[]
);
console.log(generator(callExpr).code)
// 该节点对应的js代码如下:
this.getName()
ObjectExpression
对象表达式节点。{ name:'hunly' }
这就是一个单纯的对象表达式,像这种const obj = {}
它是变量声明节点和对象表达式节点结合的体现,后面的{}
才是一个对象表达式。对象表达式节点可以用在很多地方,例如变量声明、函数传参等地方。
因为一个对象可以有很多个属性,因此properties是一个数组,该数组的元素是objectProperty节点。
js
对象表达式节点,其常见属性如下:
{
type: "ObjectExpression",
properties: [objectPropertyNode, objectPropertyNode, ...]
}
t.objectExpression(properties);
ObjectProperty
属性节点,用该节点来生成对象属性。key表示属性名,一般为变量节点。value表示属性值,一般是表达式节点、字面量节点、变量节点。如果属性是一个方法,则value是一个函数声明节点即可。
js
属性节点,其常见属性如下:
{
type: "ObjectProperty",
key: Identifier,
value: Expression,
computed: false, //(默认:false)
shorthand: false, // (默认:false)
decorators: [decorator,decorator,...] // 默认:null
}
t.objectProperty(key, value, computed, shorthand, decorators);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
const objectNode = t.objectExpression([
t.objectProperty(t.identifier('name'), t.stringLiteral('hunly'), false, false, null),
t.objectProperty(t.identifier('age'), t.numericLiteral(25),false, false, null),
])
console.log(generator(objectNode).code)
// 该节点对应的js代码如下:
{
name: "hunly",
age: 25
}
这里需要注意的是objectProperty节点的其他三个属性,即:computed、shorthand、decorators
。首先说decorators
:这是一个表示装饰器的 AST 节点的数组,用于给属性添加装饰器。如果我们想要给该属性添加装饰的话可以传入对应的装饰器节点,但是通常情况下我们不用给属性创建装饰器,因此该参数传入null即可。shorthand表示是否简写,因为在es6中,如果属性的属性值是一个变量,且该变量名称和属性名相同,就可以简写,例如{ name: name }
就可以简写为{name}
,如果在业务中有这个需要,就可以将该属性变为true,即简写模式。但只有在key和value都是变量节点,且对应的变量名相同时该属性设置为true
才有效,否则该属性无效:
js
const objectNode = t.objectExpression([
t.objectProperty(t.identifier('name'), t.identifier('name'),false, true, null), // 此时简写属性设置为true是有效的,因为ke,value是相同的变量节点。
t.objectProperty(t.identifier('age'), t.numericLiteral(25)),
])
// 该节点对应的js代码如下:
{
name,
age: 25
}
computed属性表示该属性的属性名是否可以动态得来,如果设置为true,则上面例子为:
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
const objectNode = t.objectExpression([
t.objectProperty(t.identifier('name'), t.stringLiteral('hunly'), true),// 属性名为动态得到
t.objectProperty(t.identifier('age'), t.numericLiteral(25)),
])
console.log(generator(objectNode).code)
{
[name]: "hunly",
age: 25
}
ConditionalExpression
条件表达式节点,所谓的条件表达式是我们程序中常见的? :
表达式。其中test属性一般是一个表达式节点,这个属性表示的是条件部分 ,即a ? b : c
中的a部分。一般情况下这个部分是一个能够做条件判断的表的是节点,当然也可以是一个能够进行判断的字面量节点,例如布尔字面量。而consequent属性是条件为真时需要返回的节点,一般是一个表达式节点,当然也可以是其他节点,例如字面量节点。alternate属性是当条件为false时返回的节点,一般情况下也是表达式节点,也可以是字面量等其他节点,这取决于你想返回什么,它就是什么节点。
js
条件表达式,其常见属性如下:
{
type: "ConditionalExpression",
test: Expression,
consequent: Expression,
alternate: Expression
}
t.conditionalExpression(test, consequent, alternate);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建条件表达式的三部分
const condition = t.booleanLiteral(true); // test: 这是条件表达式中的条件部分,通常是一个逻辑表达式的节点,表示要进行判断的条件。
const trueExpression = t.numericLiteral(18);// consequent: 这是条件为真时返回的表达式,通常是一个表示条件为真时的操作的节点
const falseExpression = t.numericLiteral(24);// alternate: 这是条件为假时返回的表达式,通常是一个表示条件为假时的操作的节点。
// 创建条件表达式节点
const conditionalExpr = t.conditionalExpression(condition, trueExpression, falseExpression);
console.log(generator(conditionalExpr).code)
// 该节点对应的js代码如下:
true ? 18 : 24
MemberExpression
成员表达式节点。所谓的成员表达式节点我们可以看作是a.b
这种通过.
访问成员的成员节点。该节点一般针对的是对象或数组的成员访问,因此它的三个属性是和对象或数组相关。object表示的是成员表达式的对象部分,既要访问属性或者元的对象或者数组,我们以personal.name
为例,本质上我们想要访问的是name,那么从谁那里访问呢,从personal那里访问,因此personal就是我们的对象部分,而name就是我们想要访问的成员。那么此时personal是一个Identifier节点,如果以personal.hunly.age
这种为例,本质上我们访问的是age这个成员,从哪里访问呢,其实是从personal.hunly
这里访问,因此对象部分也是一个成员表达式,因此成员表达式节点的对象部分也可以是一个成员表达式节点。
我们回到节点的属性上来,object属性就是对象部分,根据上述描述,object可以是一个变量节点也可以是一个表达式节点。而针对于property就是我们想要访问的成员部分,一般是一个变量节点。
computed表示属性名是否是动态计算的,是一个布尔值。如果为 true
,则表示属性名是动态计算的,需要放在方括号内;如果为 false
,则表示属性名是静态的。你可以认为,如果computed为true,那么是通过personal[name]
这种形式访问的。本小结最后会附录对象的代码示例。
optional表示该属性是否是可选的,如果为true则为可选属性,即personal?.name
否则为persaonl.name
。因为最新版本的babel插件把这个单独设置成为了一个OptionalMemberExpression节点,因此我们一般不会在这里设置成员的可选行为。就算是我们设置了true,它本质上也没有效果,返回的代码仍然是personal.name
而不是personal?.name
,即这个属性在该版本的方法中无效,如果设置可选属性则请使用OptionalMemberExpression节点。
js
成员表达式节点,其常见属性如下:
{
type: "MemberExpression",
object: Expression | Identifier,
property: Expression | Identifier,
computed: false,
optional(非必填): true | false | null // 该版本已无效
}
t.memberExpression(object, property, computed, optional);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types')
const object = t.identifier('obj'); // 对象标识符
const property = t.identifier('prop'); // 属性名标识符
const computed = true; // 属性名是否是动态计算的,这里为静态计算
const memberExpression = t.memberExpression(object, property, computed);
console.log(generator(memberExpression).code)
// 打印的代码如下: obj[prop]
从代码中我们可以看到如果computed为true,则访问成员的方式从.变成了[]这种形式,至于[]内部是变量还是字符串和你传入的property是什么节点有关。如果是变量节点就是obj[prop],如果是字符串字面量节点则是obj['prop']这种形式
NewExpression
new表达式,该表达式是用来执行new操作的。一般这个节点用在构造函数或者类实例化时。callee属性一般表示的是一个变量节点,即我们要实例化的构造函数或者类。而arguments属性是一个数组,该数组中的元素是什么节点取决你想要向构造函数或者类中传入什么。传入什么就是什么节点。本小结最后附录相关节点代码。
js
new表达式节点,其常见属性如下:
{
type: "NewExpression",
callee: Expression | Super | Identifier,
arguments: Array<Expression | SpreadElement | JSXNamespacedName | ArgumentPlaceholder>
}
t.newExpression(callee, arguments);
js
const { types: t } = require('@babel/core');
const callee = t.identifier('MyClass'); // 构造函数标识符
const args = [t.stringLiteral('arg1'), t.numericLiteral(2)]; // 构造函数参数列表
const newExpression = t.newExpression(callee, args);
console.log(generator(newExpression).code)
// 该节点对应的代码如下:
new MyClass('arg1', 2)
OptionalMemberExpression
可选链表达式节点。该节点和成员表达式的属性和用法基本相同,唯一的区别在于它的optional属性必须是true。普通成员访问必须使用MemberExpression节点,可选链必须使用OptionalMemberExpression节点。
js
可选链表达式,其常见属性如下:
{
type: "MemberExpression",
object: Expression | Identifier,
property: Expression | Identifier,
computed: false,
optional: true
}
t.optionalMemberExpression(object, property, computed, optional);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建可选链成员表达式节点:object?.name
const optionalMemberExpression = t.optionalMemberExpression(
t.identifier('object'), // 对象名
t.identifier('name'), // 属性名,
false,
true // 必填
);
console.log(generator(optionalMemberExpression).code)
// 该节点对应的js代码如下:
object?.name
CallExpression
调用表达式节点,该节点一般针对于函数的调用。例如getName()
就是一个调用表达式,该节点有两个比较关键的属性:cellee
表示被调用的函数或方法,一般情况下函数的调用有很多种,例如:getName()、this.getName()、obj[0]()
等调用方式,因此该属性可以是一个变量节点,成员表达式节点或者其他节点等。而arguments是一个数组,表示调用时需要传递的参数。该数组中是什么节点取决于你想要传什么类型的实参。
js
调用表达式,其常见属性如下:
{
type: "CallExpression",
callee: Expression,
arguments: Array<Expression>
}
t.callExpression(callee, arguments);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建方法调用表达式节点:console.log(personal.name)
const logMemberExpression = t.callExpression(
t.memberExpression(t.identifier('console'), t.identifier('log')),
[t.memberExpression(t.identifier('personal'),t.identifier('name'))]
)
console.log(generator(logMemberExpression).code)
// 该节点对应的js代码如下:
console.log(personal.name)
logicalExpression
逻辑表达式节点,常见的逻辑表达式一般是||(或)、&&(与)、??
,??
表示如果左边计算的结果为undefined或者null
则直接返回后者。operator表示逻辑表达式的方式,一般为"||" 、"&&" 、 "??"。因为逻辑表达式的左边就可以是例如:a || b
或者this.isNumber()
这种计算得到布尔值的表达式或者变量,因此left属性可以是一个变量节点或者表达式节点。同理right属性也可以是一个变量节点或者表达式节点。
js
逻辑表达式节点,其常见属性如下:
{
type: "LogicalExpression",
operator: "||" | "&&" | "??",
left: Expression | Identifier,
right: Expression | Identifier
}
t.logicalExpression(operator, left, right);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types');
// 创建一个表示逻辑或表达式的 AST 节点:(a || b)
const logicalExpressionNode = t.logicalExpression(
'||', // 逻辑或运算符
t.identifier('a'), // 左操作数为标识符 'a'
t.identifier('b') // 右操作数为标识符 'b'
);
console.log(generator(logicalExpressionNode).code)
// 该节点对应的js代码如下:
a || b
Class (类)
以下是常见的类相关节点,在本章节最后附录相关代码。
ClassDeclaration
该节点是类声明节点,通过class
关键字声明一个类。其中id表示类的名称,一般是一个变量节点。而superClass表示父类,表示继承哪个类。它是一个表示父类的表达式,通常是一个变量节点或成员表达式节点,表示类继承自哪个类。如果类没有显式指定父类,则该属性为 null
。而body则是类的主体部分,其是一个ClassBody节点。
js
类声明节点,其常见属性如下:
{
type: "ClassDeclaration",
id: Identifier,
superClass: Expression,
body: ClassBody
}
t.classDeclaration(id, superClass, body, decorators);
ClassBody
类主体节点,该节点是类节点的子节点,用于创建对应的类主体代码。该节点有一个body属性,该属性是一个数组。因为在es6当中,类一般情况下是由属性和方法组成,因此body数组中的节点一般情况下是classMethod和classProperty节点。
js
类主体节点,其常见属性如下:
{
type: "ClassBody",
body: [ClassMethod | ClassProperty, ClassMethod | ClassProperty]
}
t.classBody(body);
ClassProperty
类属性节点,该节点是类节点的子节点,用于生成类的属性。其中key表示的是类属性的属性名,一般是一个变量节点。而value表示属性的属性值,因为属性值可以是字面量也可以是数组、对象等,因此它可以是字面量节点或者对象或数组表达式节点。
computed表示属性键是否是一个计算属性,默认为 false
。static表示属性是否是静态属性,即是否属于类本身而不是类的实例,默认为 false。decorators表示装饰器列表,即应用于该属性的装饰器函数或装饰器工厂函数的列表。内部一般是装饰器节点。
一般情况下key和value比较常用,typeAnnotation类型注解,表示属性的类型信息,是一个 TypeAnnotation
或 TSTypeAnnotation
节点。一般情况下为null。
js
类属性节点,其常见属性如下:
{
type: "ClassProperty",
key: Identifier,
value: Literal | Expression | Identifier,
computed: false,
static: false,
decorators: []
}
t.classProperty(key, value, typeAnnotation, decorators, computed, static);
ClassMethod
类方法节点,该节点是类的子节点,通过该节点来创建类方法。kind属性表示当前方法的类型,一般情况下都是'method'
表示这是一个普通的方法,而constructor
表示这是一个constructor函数方法,get
表示这是一个get类型的方法,set
表示这是一个set类型的方法。key表示类方法名称,一般是一个变量节点。而params是该方法的形参部分,它是一个数组,内部的元素一般都是变量节点。body是方法的函数体部分,通常是一个blockStatement节点。computed表示方法是否是一个计算属性,一般为false,static表示该方法是否是一个静态方法,默认为false。
这里需要注意,现在classMethod节点所对应的方法是es6方法结构,即getName(){}
这种形式。
js
类方法节点,其常见属性如下:
{
kind: "constructor" | "method" | "get" | "set", // 方法种类
key: Identifier, // 方法名称
params: Array<Identifier | Pattern>, // 参数列表
body: BlockStatement, // 函数体
computed?: boolean, // 方法名是否是一个计算属性,默认为 false
static?: boolean // 是否是静态方法,默认为 false
}
t.classMethod(kind, key, params, body, computed, static, generator, async)
代码示例:
这里用一个简单示例来更直观的体会一下类相关的节点:
js
// JS代码示例:
class Personal {
name = 'xz'
getAge(){
return 18
}
}
js
// babel代码示例:
const t = require("@babel/types")
// 创建类属性节点
const nameProperty = t.classProperty( //通过classProperty来实现类属性节点
t.identifier("name"), // 类属性的属性名
t.stringLiteral("xz") // 类属性的属性值
)
// 创建类方法节点
const getAgeMethod = t.classMethod( // 通过classMethod来实现类方法节点
"method", // 这个参数表示方法的类型,可以是 "method"、"get" 或 "set" 之一。"method":普通方法 "get":getter 方法 "set":setter 方法
t.identifier("getAge"), // 类方法的方法名
[], // 类方法的形参,因为这里我们没有指定,就默认为空数组
t.blockStatement([ // 类方法的方法体部分,传入一个数组,每一个元素都是一个可执行语句节点
t.returnStatement(t.numericLiteral(18))
])
)
// 创建类声明节点
const personalClass = t.classDeclaration(// 通过classDeclaration来声明一个类
t.identifier("Personal"),// 类名
null,// 这个参数表示类的父类(super class),通常是一个 Identifier 节点或 null。如果没有父类,则传入 null
t.classBody([nameProperty, getAgeMethod]),// 这是一个 ClassBody 节点,包含了类的属性和方法。通常是一个由 ClassProperty 和 ClassMethod 节点组成的数组。
[] // 这是一个装饰器(decorator)数组,用于指定应用于类的装饰器。通常是一个装饰器节点的数组。
)
Modules (导入导出)
导入:
以下是常用的导入相关节点:
ImportDeclaration
导入声明节点。需要注意的是,这里的导入格式为esm,即import xxx from 'xxx'
的形式。从形式上看,导入节点比较重要的是两个部分:xxx
和'xxx'
,前者表示是以什么样的形式导入,后者表示的是从哪里导入。因此在结构上对应了specifiers和source两个属性。specifiers是一个数组,里面的元素可以是一个ImportSpecifier
、ImportDefaultSpecifier
或 ImportNamespaceSpecifier
节点,这取决你想要以什么方式导入。而source通常是一个字符串字面量,表示的是从哪里导入。
js
导入声明节点,其常见属性如下:
{
type: 'ImportDeclaration',
specifiers: [ImportSpecifier | ImportDefaultSpecifier| ImportNamespaceSpcifier],
source: StringLiteral
}
t.importDeclaration(specifiers, source);
js
// 创建一个 ImportNamespaceSpecifier AST 节点
const importNamespaceSpecifier = t.importNamespaceSpecifier(
t.identifier('foo') // local,当前模块中的引用名称
)
const importDeclaration = t.importDeclaration(
[importNamespaceSpecifier],
t.stringLiteral('./personal.js')
);
// 对应的js代码如下:
import * as foo from './personal.js'
ImportSpecifier
具名导入节点,该节点是导入声明节点的子节点。通常情况下,这种节点对应的导入代码为import { name } from 'xxx'
这种具名导入节点。该节点对应的代码为{ name }
这部分。其中local表示在当前模块中的引用名称,通常是一个变量节点。imported表示的是从源模块中实际导入的名称,通常也是一个变量节点。
这里需要注意的是,如果我们想要import { foo } from 'xxx'
这种导入格式,则local和imported需要保持一致。如果保持不一致,例如将imported参数替换为t.identifier('bar')
变量节点,则导入形式变为了import { foo as bar } from 'xxx'
。
js
具名导入节点,其常见属性如下:
{
type: 'ImportSpecifier',
local: { type: 'Identifier', name: 'foo' },
imported: { type: 'Identifier', name: 'foo' }
}
t.importSpecifier(local, imported);
js
// 创建一个 ImportNamespaceSpecifier AST 节点
const importSpecifier = t.importSpecifier(
t.identifier('foo'),
t.identifier('foo')
)
const importDeclaration = t.importDeclaration(
[importSpecifier],
t.stringLiteral('./personal.js')
);
// 对应的js代码如下:
import { foo } from './personal.js'
ImportDefaultSpecifier
默认导入节点,该节点为导入声明节点的子节点。因为默认导入的格式一般为import personal from 'xxx'
,因此其只有一个local属性,表示在当前模块中的引用名称,通常是一个变量节点。
js
默认导入节点,其常见属性如下:
{
type: 'ImportDefaultSpecifier',
local: { type: 'Identifier', name: 'foo' }
}
t.importDefaultSpecifier(local);
js
// 创建一个 ImportNamespaceSpecifier AST 节点
const importDefaultSpecifier = t.importDefaultSpecifier(
t.identifier('foo')
)
const importDeclaration = t.importDeclaration(
[importDefaultSpecifier],
t.stringLiteral('./personal.js')
);
// 对应的js代码如下:
import foo from './personal.js'
ImportNamespaceSpecifier
命名空间导入节点,该节点为导入声明节点的子节点。该节点代表的导入格式为import * as xxx from 'xxx'
。其中* as xxx
就是该节点对应的代码部分,而local
就是as后面的xxx
部分,即需要重新命名的部分,一般是一个变量节点。
js
命名空间导入节点,其常见属性如下:
{
type: 'ImportNamespaceSpecifier',
local: { type: 'Identifier', name: 'foo' }
}
t.importNamespaceSpecifier(local)
js
// 创建一个 ImportNamespaceSpecifier AST 节点
const importNamespaceSpecifier = t.importNamespaceSpecifier(
t.identifier('bar') // local,当前模块中的引用名称
)
const importDeclaration = t.importDeclaration(
[importNamespaceSpecifier],
t.stringLiteral('./personal.js')
);
// 对应的js代码如下:
import * as bar from './personal.js'
导出:
以下是常用的导出相关节点:
ExportNamedDeclaration
具名导出声明节点,该节点是针对于esm导出格式。所谓的具名导出,即export xxx
的形式,和默认导出不同的是具名导出只有export
关键字。该节点有三个常见属性:declaration、specifiers、source
。declaration表示的是导出声明,因为具名导出可以导出函数或者类声明,例如export function add(){}
,因此它一般是一个声明节点。specifiers: 这个参数是一个数组,用于指定额外的导出规范。当我们一次导出多个变量的时候会很有用,因此该属性是一个数组,元素是exportSpecifier节点,通过exportSpecifier节点来实现一次导出多个变量。
如果我们想要导出一个声明函数或者一个声明类,在构建ExportNamedDeclaration节点时只需要传入一个声明节点给第一个参数,第二个参数传入空数组,第三个参数传null
,这样就可以实现导出一个声明节点。
如果我们想要导出多个,即export { name, age }
这种格式。则第一个参数传入null
,第二个参数传入一个数组,其元素是exportSpecifier节点,表示要导出多个变量。第三个参数传null
。
对于不同场景下的导出情况,传入的参数也不同。如果三个参数都传,有些参数就会不生效,例如参数二和参数三不生效。我们可以根据自己需要导出的场景来传入不同的参数。
js
具名导出节点,其常见属性如下:
{
type: 'ExportNamedDeclaration',
declaration: DeclarationNode,
specifiers: [exportSpecifierNode, exportSpecifierNode,exportSpecifierNode, ...],
source: { type: 'StringLiteral', value: 'module-name' }
}
t.exportNamedDeclaration(declaration, specifiers, source);
js
const exportNamedDeclaration = t.exportNamedDeclaration(
t.functionDeclaration(
t.identifier('foo'), // 函数名称
[], // 参数列表为空
t.blockStatement([ // 函数体
t.expressionStatement(
t.stringLiteral('Hello, world!')
)
])
),
[
t.exportSpecifier(t.identifier('foo'), t.identifier('foo')),
t.exportSpecifier(t.identifier('bar'), t.identifier('bar'))
],
t.stringLiteral('module-name') // source,表示导出内容放置在哪个模块中
);
// 一般情况下declaration属性和specifiers属性不会同时存在,这里只是用来展示传的节点。
ExportSpecifier
具名导出节点的子节点。一般情况下该节点配合exportNamedDeclaration节点使用。该节点有两个属性,local和exported属性一般是变量节点。其作用和import导入中ImportSpecifier节点的两个属性意义相同。一般情况下,这两个属性的属性值是相同的。
js
具名导出节点的子节点,其常见属性如下:
{
type: 'ExportSpecifier',
local: { type: 'Identifier', name: 'foo' },
exported: { type: 'Identifier', name: 'bar' }
}
t.exportSpecifier(local, exported)
js
const exportSpecifier = t.exportSpecifier(
t.identifier('foo'), // local,表示当前模块中要导出的成员的本地名称
t.identifier('bar') // exported,表示导出时使用的名称
);
ExportDefaultDeclaration
默认导出节点。相对于具名导出,默认导出的方式更加灵活。不仅可以导出声明函数或者类,也可以直接导出变量或者字面量以及表达式等。因此declaration属性的是可以是FunctionDeclaration | ClassDeclaration | Expression | Identifier | Literal等节点。declaration是什么节点取决于你要导出变量是什么类型。
js
默认导出节点,其常见属性如下:
{
type: 'ExportDefaultDeclaration',
declaration: FunctionDeclaration | ClassDeclaration | Expression | Identifier | Literal
}
t.exportDefaultDeclaration(declaration);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types')
const functionDeclaration = t.identifier('foo');
const exportDefaultDeclaration = t.exportDefaultDeclaration(
functionDeclaration
);
console.log(generator(exportDefaultDeclaration).code)
// 该节点对应的js代码如下:
export default foo;
ExportAllDeclaration
全部导出声明节点。该节点只有一个常用属性,即source
属性,表示从哪里导入。该节点对应的js代码为export * from 'xxx'
。
js
全部导出声明节点,其常见属性如下:
{
type: 'ExportAllDeclaration',
source: { type: 'StringLiteral', value: './module.js' }
}
t.exportAllDeclaration(source);
Program&Directive (程序和指令)
program 是代表整个程序的节点,它有 body 属性代表程序体,存放 statement 数组,就是具体执行的语句的集合。
Program
ast的顶层节点之一。Program
节点通常包含了程序的所有主要部分,例如变量声明、函数定义、表达式语句等。它是 AST 中的顶层节点,对应整个 JavaScript 源代码文件。该节点可以被visitor函数访问到。一般情况下,对js代码的操作到这个节点结束。
该节点代表的就是当前文件(模块)对应的整个程序。其中body是一个数组,表示程序主体部分,该数组由很多节点组成。directives表示指令。所谓指令指是对程序的注解。例如对于严格模式下,我们会在代码的最前面写上'use strict'
指令,表示当前程序遵循严格模式。'use strict'
就是指令。directives是一个数组,其元素是指令节点。sourceType表示程序的源类型为脚本(script),而不是模块(module)。如果代码中涉及到导入导出,则该程序就被认为是一个模块。
js
{
type: 'Program',
body: [VariableDeclaration | ...],
directives: [Directive],
sourceType: 'script',
interpreter: null
}
t.program(body, directives, sourceType, interpreter);
Directive
指令节点,可以通过该节点常见对应的指令代码。其中value是一个DirectiveLiteral节点
js
指令节点,其常见属性如下:
{
type: 'Directive',
value: { type: 'DirectiveLiteral', value: 'use strict' }
}
t.directive(value);
DirectiveLiteral
指令节点的子节点,和Directive节点配合生成完整的指令节点。
js
指令节点的子节点,其常见属性如下:
{
type: 'DirectiveLiteral',
value: 'use strict'
}
t.directiveLiteral('use strict')
js
// 示例代码
const t = require('@babel/types');
// 创建一个 Directive 节点,表示 "use strict" 指令
const useStrictDirective = t.directive(
t.directiveLiteral('use strict') // 指令的值为 "use strict"
);
console.log(useStrictDirective);
InterpreterDirective
InterpreterDirective
(解释器指示符)通常出现在脚本文件的第一行,用于指定要执行此脚本的解释器。在类 Unix 系统中,这个指示符通常以 #!
开头,后面跟着解释器的路径。例如,在一个名为 script.js
的 JavaScript 脚本文件的第一行,你可能会看到类似这样的解释器指示符:
#!/usr/bin/env node
这个指示符告诉操作系统使用 node
解释器来执行这个脚本文件。解释器指示符是一个用于告诉操作系统如何执行脚本的机制。
js
解释器指令节点,其常见属性如下:
{
type: 'InterpreterDirective',
value: '#!/usr/bin/env node'
}
t.interpreterDirective(value);
js
const useStrictDirective = t.directive(
t.interpreterDirective('#!/usr/bin/env node')
)
File&Comment (文件和注释)
babel 的 AST 最外层节点是 File,它有 program、comments、tokens 等属性,分别存放 Program 程序体、注释、token 等,是最外层节点。
File
在Babel中,File
是一个代表整个 JavaScript 文件的抽象。它是 Babel AST 的顶层节点,包含了文件的所有内容,包括导入、导出、语句等等。program这是一个表示程序主体的 AST 节点。通常,它是一个 Program
节点,包含了文件中的所有语句和声明。comments 这是一个表示源代码中的注释的数组。每个注释通常是一个 Comment
节点,包含了注释的内容、位置等信息。tokens 这是一个表示源代码中的词法单元(tokens)的数组。每个词法单元通常是一个 Token
节点,包含了词法单元的类型、值、位置等信息。
js
文件节点,其常见属性如下:
{
type: 'File',
program: {
type: 'Program',
body: [],
directives: [],
sourceType: 'script',
interpreter: null
},
comments: [],
tokens: []
}
t.file(program, comments, tokens);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types')
// 创建一个 Program 节点,表示程序主体
const program = t.program([
// 在这里添加你的程序主体语句和声明
// 例如函数声明、变量声明等
]);
// 创建一个空数组,表示没有注释
const comments = [];
// 创建一个空数组,表示没有词法单元
const tokens = [];
// 使用 t.file 方法创建一个文件的 AST 节点
const fileNode = t.file(program, comments, tokens);
其他常见节点
decorator
装饰器节点。通常,装饰器用来修饰类或者类方法,在实例化或者类方法执行时会先执行对应的装饰器,将类或类方法的引用作为target传给装饰器函数。常见的装饰器格式为@装饰器名称
,它会被放在方法或者类的上一行来修饰对应的类或者类方法。回到装饰器节点,该节点的expression属性表示的是装饰器表达式,因为根据是否传参,装饰器可以写成@decorator
和@decorator()
两种形式,前者是一个变量节点,而后者是一个函数调用表达式,因此expression可以是一个函数调用表达式,也可以是一个变量节点。
js
装饰器节点,其常用属性如下:
{
type: "Decorator",
expression: Expression | Identifier
}
t.decorator(expression);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types')
// 创建 @preClass 装饰器节点
const preClassDecorator = t.decorator( // 通过decorator方法创建一个装饰器节点
t.callExpression(
t.identifier('preClass'), // 装饰器名称
[] // 传入到装饰器中的参数,因为我们没有向装饰器中传入参数,因此这里为空数组
)
);
const preClassDecorator_2 = t.decorator( // 通过decorator方法创建一个装饰器节点
t.identifier('preClass')
);
// preClassDecorator就是一个装饰器节点,可以配合着类节点使用
// 创建一个类声明节点,并将 @preClass 装饰器应用于该类
const classDeclaration = t.classDeclaration(
t.identifier('MyClass'),
null,
t.classBody([]),
[preClassDecorator, preClassDecorator_2] // 这里写两个装饰器节点的目的是想告诉开发者,装饰器节点的expression可以是一个变量节点
);
console.log(generator(classDeclaration).code)
// 节点对应的代码如下:
@preClass()
@preClass
class MyClass {}
arrayPattern
数组解构赋值节点。一般情况下,数组的解构赋值节点不会单独存在,而是配合其他节点使用,例如变量声明节点。因为数组结构赋值节点对应的代码是[a,b]
这种单纯的表达式形式,和其他节点配合起来才具有实际意义,例如const [a,b] = array
。本小结最后附录相关代码。
回到arrayPattern节点,该节点的elements属性是一个数组,该数组的元素一般是变量节点,表示的是解构赋值时的变量。
js
数组结构赋值节点,其常见属性如下:
{
type: 'ArrayPattern',
elements: [Identifier,Identifier, ...]
}
t.arrayPattern(elements);
js
// js代码如下:
const [ a, b ] = array
// 对应的节点创建如下:
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types')
// 创建标识符节点
const identifierA = t.identifier('a');
const identifierB = t.identifier('b');
const identifierArray = t.identifier('array');
// 创建解构赋值模式的数组模式节点
const arrayPattern = t.arrayPattern([identifierA, identifierB]);
// 创建解构赋值语句的左侧节点
const left = t.variableDeclaration('const', [t.variableDeclarator(arrayPattern)]);
// 创建解构赋值语句的右侧节点
const right = identifierArray;
// 创建解构赋值语句节点
const destructuringAssignment = t.variableDeclaration('const', [
t.variableDeclarator(arrayPattern, right)
]);
console.log(generator(destructuringAssignment).code)
objectPattern
对象解构赋值节点。和数组解构赋值相同,它也不会单独存在,而是和其他例如变量声明节点一起存在。例如const { name, age } = personal
。从代码中我们可以看到,其实{name, age}
这部分才是对应的对象解构赋值。该节点常用属性为properties,它是一个数组,该数组的元素是ObjectProperty节点。本小结附录相关代码。
js
对象解构赋值节点,其常见属性如下:
{
type: 'ObjectPattern',
properties: [ObjectProperty, ObjectProperty, ...]
}
t.objectPattern(properties);
js
// cjs模式下的源码转换
const { parse, traverse } = require('@babel/core')
const generator = require('@babel/generator').default
const t = require('@babel/types')
// 创建对象解构赋值的左侧节点:{ name, age }
const objectPattern = t.objectPattern([ // 通过objectPattern创建对象的解构赋值节点
t.objectProperty(t.identifier('name'), t.identifier('name'), false, true),
t.objectProperty(t.identifier('age'), t.identifier('age'), false, true)
]);
// 创建 const 声明语句节点
const declaration = t.variableDeclaration('const', [
t.variableDeclarator(objectPattern, t.identifier('person'))
]);
console.log(generator(declaration).code)
// 对应代码如下:
const {
name,
age
} = person;
这里需要注意的是,因为我们这里使用的是es6属性简写,因此objectProperty的第四个参数需要传入true表示需要使用简写。一般情况下,如果我们想要让对象的属性是简写形式,这个参数一般都要传true。
spreadElement
拓展运算符节点。一般情况下,拓展运算符作用于数组,即...array
。该节点的argument属性表示需要展开的变量名称,一般是一个变量节点。
js
拓展运算符节点,其常见属性如下:
{
type: 'SpreadElement',
argument: Identifier
}
t.spreadElement(argument)