抽象语法树AST(Abstract Syntax Tree)

news2025/7/18 13:34:46

抽象语法树(Abstract Syntax Tree)

  • 抽象语法树(Abstract Syntax Tree,AST)是源代码语法结构的一种抽象表示
  • 它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构

抽象语法树用途

  • 代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等
    • 如JSLint、JSHint对代码错误或风格的检查,发现一些潜在的错误
    • IDE的错误提示、格式化、高亮、自动补全等等
  • 代码混淆压缩
    • UglifyJS2等
  • 优化变更代码,改变代码结构使达到想要的结构
    • 代码打包工具webpack、rollup等等
    • CommonJS、AMD、CMD、UMD等代码规范之间的转化
    • CoffeeScript、TypeScript、JSX等转化为原生Javascript

AST过程

解析过程

AST整个解析过程分为两个步骤

  • 分词:将整个代码字符串分割成语法单元数组
  • 语法分析:建立分析语法单元之间的关系

语法单元

Javascript 代码中的语法单元主要包括以下这么几种

  • 关键字:constletvar
  • 标识符:可能是一个变量,也可能是 if、else 这些关键字,又或者是 true、false 这些常量
  • 运算符
  • 数字
  • 空格
  • 注释

词法分析

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>' }
]

语法分析

  • 语义分析则是将得到的词汇进行一个立体的组合,确定词语之间的关系
  • 简单来说语法分析是对语句和表达式识别,这是个递归过程
// 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"
}

抽象语法树定义

  • 这些工具的原理都是通过JavaScript Parser把代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构,通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作

ast

JavaScript Parser

  • avaScript Parser,把js源码转化为抽象语法树的解析器。
  • 浏览器会把js源码通过解析器转为抽象语法树,再进一步转化为字节码或直接生成机器码。
  • 一般来说每个js引擎都会有自己的抽象语法树格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了详细SpiderMonkey AST format的详细说明,算是业界的标准。

常用的JavaScript Parser

  • esprima
  • traceur
  • acorn
  • babel parser
  • shift
  • estree

AST节点

  • estree
  • spec.md
  • astexplorer
  • AST节点
    • File 文件
    • Program 程序
    • Literal 字面量 NumericLiteral StringLiteral BooleanLiteral
    • Identifier 标识符
    • Statement 语句
    • Declaration 声明语句
    • Expression 表达式
    • Class 类

AST遍历

  • astexplorer
  • AST是深度优先遍历
npm i esprima estraverse escodegen -S
let esprima = require('esprima');//把JS源代码转成AST语法树
let estraverse = require('estraverse');///遍历语法树,修改树上的节点
let escodegen = require('escodegen');//把AST语法树重新转换成代码
let code = `function ast(){}`;
let ast = esprima.parse(code);
let indent = 0;
const padding = ()=>" ".repeat(indent);
estraverse.traverse(ast,{
    enter(node){
        console.log(padding()+node.type+'进入');
        if(node.type === 'FunctionDeclaration'){
            node.id.name = 'newAst';
        }
        indent+=2;
    },
    leave(node){
        indent-=2;
        console.log(padding()+node.type+'离开');
    }
});
Program进入
  FunctionDeclaration进入
    Identifier进入
    Identifier离开
    BlockStatement进入
    BlockStatement离开
  FunctionDeclaration离开
Program离开

esprima

  • 通过 esprima 把源码转化为AST
  • 通过 estraverse 遍历并更新AST
  • 通过 escodegen 将AST重新生成源码
  • astexplorer AST的可视化工具
mkdir hsast
cd hsast

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

babel

  • Babel 能够转译 ECMAScript 2015+ 的代码,使它在旧的浏览器或者环境中也能够运行
  • 工作过程分为三个部分
    • Parse(解析) 将源代码转换成抽象语法树,树上有很多的estree节点
    • Transform(转换) 对抽象语法树进行转换
    • Generate(代码生成) 将上一步经过转换过的抽象语法树生成新的代码

babel 插件

  • @babel/parser 可以把源码转换成AST
  • @babel/traverse用于对 AST 的遍历,维护了整棵树的状态,并且负责替换、移除和添加节点
  • @babel/generate 可以把AST生成源码,同时生成sourcemap
  • @babel/types 用于 AST 节点的 Lodash 式工具库, 它包含了构造、验证以及变换 AST 节点的方法,对编写处理 AST 逻辑非常有用
  • @babel/template可以简化AST的创建逻辑
  • @babel/code-frame可以打印代码位置
  • @babel/core Babel 的编译器,核心 API 都在这里面,比如常见的 transform、parse,并实现了插件功能
  • babylon Babel 的解析器,以前叫babel parser,是基于acorn扩展而来,扩展了很多语法,可以支持es2020、jsx、typescript等语法
  • babel-types-api
  • Babel 插件手册
  • babeljs.io babel 可视化编译器
  • babel-types
  • 类型别名
  • DefinitelyTyped

Visitor

  • 访问者模式 Visitor 对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同
  • Visitor 的对象定义了用于 AST 中获取具体节点的方法
  • Visitor 上挂载以节点 type 命名的方法,当遍历 AST 的时候,如果匹配上 type,就会执行对应的方法

path

  • path
  • node 当前 AST 节点
  • parent 父 AST 节点
  • parentPath 父AST节点的路径
  • scope 作用域
  • get(key) 获取某个属性的 path
  • set(key, node) 设置某个属性
  • is类型(opts) 判断当前节点是否是某个类型
  • find(callback) 从当前节点一直向上找到根节点(包括自己)
  • findParent(callback)从当前节点一直向上找到根节点(不包括自己)
  • insertBefore(nodes) 在之前插入节点
  • insertAfter(nodes) 在之后插入节点
  • replaceWith(replacement) 用某个节点替换当前节点
  • replaceWithMultiple(nodes) 用多个节点替换当前节点
  • replaceWithSourceString(replacement) 把源代码转成AST节点再替换当前节点
  • remove() 删除当前节点
  • traverse(visitor, state) 遍历当前节点的子节点,第1个参数是节点,第2个参数是用来传递数据的状态
  • skip() 跳过当前节点子节点的遍历

scope

  • scope
  • scope.bindings 当前作用域内声明所有变量
  • scope.path 生成作用域的节点对应的路径
  • scope.references 所有的变量引用的路径
  • getAllBindings() 获取从当前作用域一直到根作用域的集合
  • getBinding(name) 从当前作用域到根使用域查找变量
  • getOwnBinding(name) 在当前作用域查找变量
  • parentHasBinding(name, noGlobals) 从当前父作用域到根使用域查找变量
  • removeBinding(name) 删除变量
  • hasBinding(name, noGlobals) 判断是否包含变量
  • moveBindingTo(name, scope) 把当前作用域的变量移动到其它作用域中
  • generateUid(name) 生成作用域中的唯一变量名,如果变量名被占用就在前面加下划线

转换箭头函数

  • astexplorer
  • babel-plugin-transform-es2015-arrow-functions
  • babeljs.io babel 可视化编译器
  • babel-handbook
  • babel-types-api

转换前

const sum = (a,b)=>{
    console.log(this);
    return a+b;
}

转换后

var _this = this;

const sum = function (a, b) {
  console.log(_this);
  return a + b;
};
npm i @babel/core @babel/types -D

实现

//babel核心模块
const core = require('@babel/core');
//用来生成或者判断节点的AST语法树的节点
let types = require("@babel/types");
//let arrowFunctionPlugin = require('babel-plugin-transform-es2015-arrow-functions');
let arrowFunctionPlugin = {
    visitor: {
        //如果是箭头函数,那么就会进来此函数,参数是箭头函数的节点路径对象
        ArrowFunctionExpression(path) {
            let { node } = path;
            hoistFunctionEnvironment(path);
            node.type = 'FunctionExpression';
            let body = node.body;
            //如果函数体不是语句块
            if (!types.isBlockStatement(body)) {
                node.body = types.blockStatement([types.returnStatement(body)]);
            }
        }
    }
}
/**
 * 1.要在函数的外面声明一个_this变量,值是this
 * 2.在函数的内容,换this 变成_this
 * @param {*} path 
 */
function hoistFunctionEnvironment(path) {
    //1.确定我要用哪里的this 向上找不是箭头函数的函数或者根节点
    const thisEnv = path.findParent(parent => {
        return (parent.isFunction() && !path.isArrowFunctionExpression()) || parent.isProgram();
    });
    let thisBindings = '_this';
    let thisPaths = getThisPaths(path);
    if (thisPaths.length>0) {
        //在thisEnv这个节点的作用域中添加一个变量 变量名为_this, 值 为this var _this = this;
        if (!thisEnv.scope.hasBinding(thisBindings)) {
            thisEnv.scope.push({
                id: types.identifier(thisBindings),
                init: types.thisExpression()
            });
        }
    }
    thisPaths.forEach(thisPath => {
        //this=>_this
        thisPath.replaceWith(types.identifier(thisBindings));
    });
}
function getThisPaths(path){
    let thisPaths = [];
    path.traverse({
        ThisExpression(path) {
            thisPaths.push(path);
        }
    });
    return thisPaths;
}
let sourceCode = `
const sum = (a, b) => {
    console.log(this);
    const minus = (c,d)=>{
          console.log(this);
        return c-d;
    }
    return a + b;
}
`;
let targetSource = core.transform(sourceCode, {
    plugins: [arrowFunctionPlugin]
});

console.log(targetSource.code);

把类编译为 Function

  • @babel/plugin-transform-classes

es6

class Person {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
}

classast

es5

function Person(name) {
  this.name = name;
}
Person.prototype.getName = function () {
  return this.name;
};

es5class1 es5class2

实现

//babel核心模块
const core = require('@babel/core');
//用来生成或者判断节点的AST语法树的节点
let types = require("@babel/types");
//let transformClassesPlugin = require('@babel/plugin-transform-classes');
let transformClassesPlugin = {
    visitor: {
        //如果是箭头函数,那么就会进来此函数,参数是箭头函数的节点路径对象
        //path代表路径,node代表路径上的节点
        ClassDeclaration(path) {
            let node = path.node;
            let id = node.id;//Identifier name:Person
            let methods = node.body.body;//Array<MethodDefinition>
            let nodes = [];
            methods.forEach(method => {
                if (method.kind === 'constructor') {
                    let constructorFunction = types.functionDeclaration(
                        id,
                        method.params,
                        method.body
                    );
                    nodes.push(constructorFunction);
                } else {
                    let memberExpression = types.memberExpression(
                        types.memberExpression(
                            id, types.identifier('prototype')
                        ), method.key
                    )
                    let functionExpression = types.functionExpression(
                        null,
                        method.params,
                        method.body
                    )
                    let assignmentExpression = types.assignmentExpression(
                        '=',
                        memberExpression,
                        functionExpression
                    );
                    nodes.push(assignmentExpression);
                }
            })
            if (nodes.length === 1) {
                //单节点用replaceWith
                //path代表路径,用nodes[0]这个新节点替换旧path上现有老节点node ClassDeclaration
                path.replaceWith(nodes[0]);
            } else {
                //多节点用replaceWithMultiple
                path.replaceWithMultiple(nodes);
            }
        }
    }
}
let sourceCode = `
class Person{
    constructor(name){
        this.name = name;
    }
    sayName(){
        console.log(this.name);
    }
}
`;
let targetSource = core.transform(sourceCode, {
    plugins: [transformClassesPlugin]
});

console.log(targetSource.code);

实现日志插件

logger.js

//babel核心模块
const core = require('@babel/core');
//用来生成或者判断节点的AST语法树的节点
const types = require("@babel/types");
const path = require('path');
const visitor = {
    CallExpression(nodePath, state) {
        const { node } = nodePath;
        if (types.isMemberExpression(node.callee)) {
            if (node.callee.object.name === 'console') {
                if (['log', 'info', 'warn', 'error', 'debug'].includes(node.callee.property.name)) {
                    const { line, column } = node.loc.start;
                    const relativeFileName = path.relative(__dirname, state.file.opts.filename).replace(/\\/g, '/');
                    node.arguments.unshift(types.stringLiteral(`${relativeFileName} ${line}:${column}`));
                }
            }
        }
    }
}
module.exports = function () {
    return {
        visitor
    }
}
/* {
    loc: {
        start: { line: 1, column: 1 }
    }
} */

自动日志插件

  • babel-helper-plugin-utils
  • babel-types用来生成节点和判断节点类型
  • babel-helper-module-imports帮助插入模块
  • @babel/template根据字符串模板生成AST节点
  • state 用于在遍历过程中在AST节点之间传递数据的方式

use.js

const { transformSync } = require('@babel/core');
const autoLoggerPlugin = require('./auto-logger-plugin');
const sourceCode = `
function sum(a,b){return a+b;}
const multiply = function(a,b){return a*b;};
const minus = (a,b)=>a-b
class Calculator{divide(a,b){return a/b}}
`
const { code } = transformSync(sourceCode, {
  plugins: [autoLoggerPlugin({ libName: 'logger' })]
});
console.log(code);

auto-logger-plugin

const importModule = require('@babel/helper-module-imports');
const template = require('@babel/template');
const types = require('@babel/types');
const autoLoggerPlugin = (options) => {
    return {
        visitor: {
            Program: {
                enter(path, state) {
                    let loggerId;
                    path.traverse({
                        ImportDeclaration(path) {
                            const libName = path.get('source').node.value;
                            if (libName === options.libName) {
                                const specifierPath = path.get('specifiers.0');
                                //import logger from 'logger'
                                //import { logger } from 'logger';
                                //import * as logger from 'logger';
                                if (specifierPath.isImportDefaultSpecifier()
                                    || specifierPath.isImportSpecifier()
                                    || specifierPath.isImportNamespaceSpecifier()) {
                                    loggerId = specifierPath.local.name;
                                }
                                path.stop();
                            }
                        }
                    });
                    if (!loggerId) {
                        loggerId = importModule.addDefault(path, 'logger', {
                            nameHint: path.scope.generateUid('logger')
                        }).name;
                    }
                    //state.loggerNode = types.expressionStatement(types.callExpression(types.identifier(loggerId), []));
                    //state.loggerNode = template.statement(`${loggerId}();`)();
                    state.loggerNode = template.statement(`LOGGER();`)({
                        LOGGER: loggerId
                    });
                }
            },
            'FunctionExpression|FunctionDeclaration|ArrowFunctionExpression|ClassMethod'(path, state) {
                const { node } = path
                if (types.isBlockStatement(node.body)) {
                    node.body.body.unshift(state.loggerNode);
                } else {
                    const newNode = types.blockStatement([
                        state.loggerNode,
                        types.expressionStatement(node.body)
                    ]);
                    path.get('body').replaceWith(newNode);
                }
            }
        }
    }
};
module.exports = autoLoggerPlugin;

eslint

  • rules

use.js

const { transformSync } = require('@babel/core');
const eslintPlugin = require('./eslintPlugin');
const sourceCode = `
var a = 1;
console.log(a);
var b = 2;
`;
const { code } = transformSync(sourceCode, {
  plugins: [eslintPlugin({ fix: true })]
});
console.log(code);

eslintPlugin.js

eslintPlugin.js

//no-console 禁用 console
const eslintPlugin = ({ fix }) => {
  return {
    pre(file) {
      file.set('errors', []);
    },
    visitor: {
      CallExpression(path, state) {
        const errors = state.file.get('errors');
        const { node } = path
        if (node.callee.object && node.callee.object.name === 'console') {
          //const tmp = Error.stackTraceLimit;//可以修改堆栈信息的深度,默认为10
          //Error.stackTraceLimit = 0;
          errors.push(path.buildCodeFrameError(`代码中不能出现console语句`, Error));
          //Error.stackTraceLimit = tmp;
          if (fix) {
            path.parentPath.remove();
          }
        }
      }
    },
    post(file) {
      console.log(...file.get('errors'));
    }
  }
};
module.exports = eslintPlugin;

uglify

use.js

const { transformSync } = require('@babel/core');
const uglifyPlugin = require('./uglifyPlugin');
const sourceCode = `
function getAge(){
  var age = 12;
  console.log(age);
  var name = '';
  console.log(name);
}
`;
const { code } = transformSync(sourceCode, {
  plugins: [uglifyPlugin()]
});
console.log(code);

uglifyPlugin.js

    • 类型别名

uglifyPlugin.js

const uglifyPlugin = () => {
  return {
    visitor: {
      Scopable(path) {
        Object.entries(path.scope.bindings).forEach(([key, binding]) => {
          const newName = path.scope.generateUid();
          binding.path.scope.rename(key, newName)
        });
      }
    }
  }
};
module.exports = uglifyPlugin;

webpack中使用babel插件

实现按需加载

  • lodashjs
  • babel-core
  • babel-plugin-import
import { flatten, concat } from "lodash";

treeshakingleft

转换为

import flatten from "lodash/flatten";
import concat from "lodash/flatten";

treeshakingright

webpack 配置

npm i webpack webpack-cli babel-plugin-import -D
const path = require("path");
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: path.resolve("dist"),
    filename: "bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: "babel-loader",
          options:{
                   plugins:[
                     [
                       path.resolve(__dirname,'plugins/babel-plugin-import.js'),
                       {
                         libraryName:'lodash'
                       }
                     ]
                   ]
                }
        },
      },
    ],
  },
};

编译顺序为首先plugins从左往右,然后presets从右往左

babel 插件

plugins\babel-plugin-import.js

//babel核心模块
const core = require('@babel/core');
//用来生成或者判断节点的AST语法树的节点
let types = require("@babel/types");

const visitor = {
    ImportDeclaration(path, state) {
        const { node } = path;//获取节点
        const { specifiers } = node;//获取批量导入声明数组
        const { libraryName, libraryDirectory = 'lib' } = state.opts;//获取选项中的支持的库的名称
        //如果当前的节点的模块名称是我们需要的库的名称
        if (node.source.value === libraryName
            //并且导入不是默认导入才会进来
            && !types.isImportDefaultSpecifier(specifiers[0])) {
            //遍历批量导入声明数组
            const declarations = specifiers.map(specifier => {
                //返回一个importDeclaration节点
                return types.importDeclaration(
                    //导入声明importDefaultSpecifier flatten
                    [types.importDefaultSpecifier(specifier.local)],
                    //导入模块source lodash/flatten
                    types.stringLiteral(libraryDirectory ? `${libraryName}/${libraryDirectory}/${specifier.imported.name}` : `${libraryName}/${specifier.imported.name}`)
                );
            })
            path.replaceWithMultiple(declarations);//替换当前节点
        }
    }
}


module.exports = function () {
    return {
        visitor
    }
}

参考

  • Babel 插件手册
  • babel-types
  • 不同的 parser 解析 js 代码后得到的 AST
  • 在线可视化的看到 AST
  • babel 从入门到入门的知识归纳
  • Babel 内部原理分析
  • babel-plugin-react-scope-binding
  • transform-runtime Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译,启用插件 babel-plugin-transform-runtime 后,Babel 就会使用 babel-runtime 下的工具函数
  • ast-spec
  • babel-handbook

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1822218.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

辣椒属2个T2T基因组-文献精读23

Two telomere-to-telomere gapless genomes reveal insights into Capsicum evolution and capsaicinoid biosynthesis 两个端粒到端粒无缝基因组揭示了辣椒进化和辣椒素生物合成的相关见解 摘要 辣椒&#xff08;Capsicum&#xff09;因其果实中含有辣椒素而闻名&#xff0c…

【SQL每日一练】获取北纬度(LAT_N)的中位数

文章目录 前言一、题析二、题解1.mysql2.sqlserver 前言 从 STATION 查询北纬度 &#xff08;LAT_N&#xff09; 的中位数&#xff0c;并将您的答案四舍五入到小数点后4位. 中位数的定义是&#xff1a;如果数据量是奇数&#xff0c;则中位数是排序后位于中间的数&#xff1b;如…

拥抱数字世界|AI在娱乐行业的应用,娱乐新纪元已到来

在蓬勃发展的全球化趋势下&#xff0c;越来越多的厂商正在批量涌入娱乐赛道&#xff0c;期待能创造新的增长奇迹。随着科技的不断发展&#xff0c;人工智能技术正日益深入各行各业&#xff0c;其中媒体和娱乐行业更是迎来了一场革命性的变革。在媒体和娱乐领域展现出了巨大的潜…

海康威视-NVR使用及ISAPI协议透传接入

目录 1、初始化配置 1.1、设置通道默认密码 1.2、添加摄像头 1.3、设置不采集时间段 1.4、抓拍延迟设置 1.5、录像保存时长设置 1.6、人脸库维护 1.7、导入照片 1.8、设置事件 1.8.1、引擎配置 1.8.2、事件设置 1.8.2.1、目标比对 1.8.2.2、设置屏蔽区 1.8.2.3、…

每日一练:攻防世界:北京地铁

首先是找图片隐写 在这里可以看到一串类似base64格式的字符串 再结合题目&#xff0c;这应该就是明文了&#xff0c;要AES解密&#xff0c;还需要密钥&#xff0c;提示要看图片本身&#xff0c;那密钥可能藏在里面&#xff0c;找了半天没找到&#xff0c;参考师傅的wp&#x…

Docker:利用Docker搭建一个nginx服务

文章目录 搭建一个nginx服务认识nginx服务Web服务器反向代理服务器高性能特点 安装nginx启动nginx停止nginx查找nginx镜像拉取nginx镜像&#xff0c;启动nginx站点其他方式拉取nginx镜像信息通过 DIGEST 拉取镜像 搭建一个nginx服务 首先先认识一下nginx服务&#xff1a; NGI…

FreeRTOS 简单内核实现1 前言

文章目录 0、写在前面1、参考资料2、准备工作2.1、STM32 空工程2.2、创建 RTOS 文件目录 3、约定4、专栏目录5、项目仓库 0、写在前面 为深入理解 RTOS 内核工作机制&#xff0c;笔者制作了名为 “FreeRTOS 内核简单实现” 的项目专栏 &#xff0c;目标为自己动手从 0 到 1 编…

第二证券股市资讯:苹果,重回第一!

苹果以弱小的优势&#xff0c;从头夺回市值榜首宝座。 当地时间6月13日周四&#xff0c;美股三大股指涨跌纷歧&#xff0c;纳指与标普500指数均录得接连第四日上涨&#xff0c;而且再创前史新高。 周四&#xff0c;美国5月份生产者价格指数&#xff08;PPI&#xff09;意外下…

Apache Doris单机快速安装(已踩坑)

官方文档&#xff1a;https://doris.incubator.apache.org/zh-CN/docs/get-starting/quick-start/ 环境&#xff1a; 操作系统&#xff1a;CentOS7.6 X86_64 JDK&#xff1a;Oracle jdk1.8.0_351 1.版本下载 从 doris.apache.org 下载相应的 Doris 安装包&#xff0c;并且解压…

碎片化知识如何被系统性地吸收?

一、方法论 碎片化知识指的是通过各种渠道快速获取的零散信息和知识点&#xff0c;这些信息由于其不完整性和孤立性&#xff0c;不易于记忆和应用。为了系统性地吸收碎片化知识&#xff0c;可以采用以下策略&#xff1a; 1. **构建知识框架**&#xff1a; - 在开始吸收之前&am…

吉时利Keithley2611B单通道SMU数字源表

Keithley吉时利2611B数字源表 2611B、2612B、2614B 系统 Sourcemeter SMU 仪器 2611B、2612B 和 2634B 系统 Sourcemeter SMU 仪器为 30W DC / 200W 脉冲 SMU&#xff0c;支持 10A 脉冲&#xff0c;1.5A 至 100fA 和 200V 至 100nV DC。所有 2600B SMU 均配备吉时利 TSP 脚本…

硬件开发笔记(十八):核心板与底板之间的连接方式介绍说明:板对板连接器

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/139663096 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

基于SSD的安全帽检测

目录 1. 作者介绍2. SSD算法介绍2.1 SSD算法网络结构2.2 SSD算法训练过程2.3 SSD算法优缺点 3. 基于SSD的安全帽检测实验3.1 VOC 2007安全帽数据集3.2 SSD网络架构3.3 训练和验证所需的2007_train.txt和2007_val.txt文件生成3.4 模型训练3.5 GUI界面3.6 结果展示3.7 文件下载 4…

C#版 iText7——画发票PDF(完整)

显示描述&#xff1a; 1、每页显示必须带有发票头、“销售方和购买方信息” 2、明细填充为&#xff1a;当n≤8 行时&#xff0c;发票总高度140mm&#xff0c;每条发票明细行款高度4.375mm&#xff1b; 当8<n≤12行时&#xff0c;发票高度增加17.5mm&#xff0c;不换页&#…

人工智能内容生成元年-AI绘画原理解析

随着人工智能技术的飞速发展&#xff0c;AI绘画作为其引人注目的应用领域&#xff0c;正在以惊人的速度崭露头角。从最初的生成对抗网络&#xff08;GAN&#xff09;到如今的深度学习&#xff0c;AI绘画技术在艺术创作、设计等领域展现出了无限的可能性。其独特的算法和智能化特…

构建 deno/fresh 的 docker 镜像

众所周知, 最近 docker 镜像的使用又出现了新的困难. 但是不怕, 窝们可以使用曲线救国的方法: 自己制作容器镜像 ! 下面以 deno/fresh 举栗, 部署一个简单的应用. 目录 1 创建 deno/fresh 项目2 构建 docker 镜像3 部署和测试4 总结与展望 1 创建 deno/fresh 项目 执行命令…

情侣飞行棋系统微信小程序+H5+微信公众号+APP 源码

情侣飞行棋系统&#xff1a;浪漫与策略并存的双人游戏 &#x1f3b2; 一、引言&#xff1a;寻找爱情的乐趣 在繁忙的生活中&#xff0c;情侣们总是渴望找到一种既能增进感情又能带来乐趣的活动。而“情侣飞行棋系统”正是这样一个完美的选择。它结合了传统飞行棋的玩法和情侣…

接口自动化测试工程化——了解接口测试

什么是接口测试 接口测试也是一种功能测试 我理解的接口测试&#xff0c;其实也是一种功能测试&#xff0c;只是平时大家说的功能测试更多代指 UI 层面的功能测试&#xff0c;而接口测试更偏向于服务端层面的功能测试。 接口测试的目的 测试左移&#xff0c;尽早介入测试&a…

失眠焦虑?这些小妙招助你重拾宁静之夜

在这个快节奏的时代&#xff0c;失眠与焦虑似乎成了不少人的“常客”。每当夜幕降临&#xff0c;躺在床上却辗转反侧&#xff0c;思绪万千&#xff0c;仿佛整个世界的喧嚣都涌入了脑海。&#x1f4ad; 其实&#xff0c;放松心情&#xff0c;调整心态&#xff0c;是缓解失眠焦虑…

【MATLAB源码-第225期】基于matlab的计算器GUI设计仿真,能够实现基础运算,三角函数以及幂运算。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 界面布局 计算器界面的主要元素分为几大部分&#xff1a;显示屏、功能按钮、数字按钮和操作符按钮。 显示屏 显示屏&#xff08;Edit Text&#xff09;&#xff1a;位于界面顶部中央&#xff0c;用于显示用户输入的表达式和…