Что такое абстрактное синтаксическое дерево (Abstract Syntax Tree ,AST
)?
Википедия объясняет это так:
В информатике абстрактное синтаксическое дерево (AST) или просто синтаксическое дерево — это абстрактное представление грамматической структуры исходного кода. Он представляет синтаксическую структуру языка программирования в виде дерева, и каждый узел в дереве представляет собой структуру в исходном коде.
Это все еще звучит запутанно, это не имеет значения, вы можете просто понять это какЭто древовидное представление кода, который вы пишете..
С помощью этого дерева мы можем точно находить операторы объявления, операторы присваивания, операторы операций и т. д., манипулируя деревом, и выполнять анализ, оптимизацию и изменения кода.
С AST может быть сложно иметь дело в повседневном бизнесе.Вы можете не слышать о нем, но на самом деле много раз уже используете его, но не обращаете на него особого внимания.Сейчас он популярен.webpack
,eslint
Задействовано множество плагинов или пакетов~
Что могут абстрактные синтаксические деревья?
говорить сAST
Его применение очень обширно, я кратко перечислю некоторые из них ниже:
-
IDE
подсказки об ошибках, форматирование кода, подсветка кода, автодополнение кода и т. д. -
JSLint
,JSHint
Проверяет наличие ошибок кода или стиля и т. д. -
webpack
,rollup
кодовая упаковка и т.д. -
CoffeeScript
,TypeScript
,JSX
и т.д. в роднойJavascript
На самом деле его применения намного больше.Если вас не устраивает реализация скучных бизнес-функций, вы можете написать что-то вродеreact
,vue
Такой мощный фреймворк или хотите создать аналогичный наборwebpack
,rollup
Такой передний автоматизированный инструмент упаковки, то вы должны пониматьAST
.
Как сгенерировать АСТ?
Узнайте, как генерироватьAST
Прежде необходимо понятьParser
(ОбщийParser
имеютesprima
,traceur
,acorn
,shift
Ждать)
JS Parser
По сути, это парсер, который конвертируетjs
Исходный код преобразуется в абстрактное синтаксическое дерево (AST
) парсер.
Весь процесс анализа в основном делится на следующие два этапа:
- Сегментация слов: разбивает всю строку кода на массив минимальных синтаксических единиц.
- Синтаксический анализ: установление и анализ отношений между грамматическими единицами на основе сегментации слов.
Что такое синтаксическая единица?
Грамматическая единица — это наименьшая единица с фактическим значением в проанализированной грамматике, которая представляет собой просто слово в естественном языке.
Например, следующее предложение:
«2019 год – 70-летие Родины»
Мы можем разбить это предложение на самые маленькие единицы, а именно: 2019 год, да, родина, 70-летие, юбилей.
Это то, что мы называем причастием, причем наименьшая единица, потому что, если мы его расчленим, оно не будет иметь практического значения.
Javascript
Синтаксические единицы в коде в основном включают следующее:
- Ключевые слова: например
var
,let
,const
Ждать - Идентификатор: последовательные символы, не заключенные в кавычки, могут быть переменной, могут быть
if
,else
эти ключевые слова илиtrue
,false
Эти встроенные константы - Оператор:
+
,-
,*
,/
Ждать - Числа: синтаксисы, такие как шестнадцатеричные, десятичные, восьмеричные и научные выражения.
- Строка: потому что для компьютера содержимое строки будет участвовать в вычислении или отображении
- пробелы: последовательные пробелы, разрывы строк, отступы и т. д.
- Комментарий: строчный комментарий или блочный комментарий — это наименьшая неделимая синтаксическая единица.
- Другие: фигурные скобки, круглые скобки, точки с запятой, двоеточия и т. д.
Если мы возьмем простейший оператор копирования в качестве примера, как показано ниже?
var a = 1;
Путем сегментации слов мы можем получить следующие результаты:
[
{
"type": "Keyword",
"value": "var"
},
{
"type": "Identifier",
"value": "a"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "Numeric",
"value": "1"
},
{
"type": "Punctuator",
"value": ";"
}
]
Для удобства я сразуesprima/parserЭтот веб-сайт генерирует сегментацию слов ~
Что такое синтаксический анализ?
Мы получили результат нашей сегментации слов выше.Нам нужно составить трехмерную комбинацию слов, определить отношения между словами и определить окончательное значение выражения слов.
Короче говоря, синтаксический анализ предназначен для идентификации операторов и выражений и определения предыдущей связи, что является рекурсивным процессом.
Проведя приведенный выше синтаксический анализ, мы можем получить следующие результаты:
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "Literal",
"value": 1,
"raw": "1"
}
}
],
"kind": "var"
}
],
"sourceType": "script"
}
Этоvar a = 1
преобразованныйAST
;
Здесь рекомендуютastexplorer AST
инструменты визуализации,astexplorer, можно переходить непосредственно к кодуAST
конвертировать~
Как именно используется АСТ?
Я нарисовал много места выше и рассказал об этомAST
что это такое и как оно образовалось, ведь до сих пор не знаюAST
Какая польза от этой штуки и как ею пользоваться. .
хорошо~ Далее, давайте вместе станем свидетелями чуда.
Я считаю, что большинство студентовbabel
Эта библиотека не является чем-то незнакомым, она должна быть незаменима в процессе фронтенд-модульной разработки, потому что может помочь вамECMAScript 2015+
код версии преобразован в обратно совместимыйJavaScript
синтаксис, чтобы он мог работать в текущих и более старых версиях браузеров или других средах, вам не нужно беспокоиться о совместимости с новым синтаксисом~
И на самом деле,babel
Многие функции вAST
осуществленный.
Во-первых, давайте рассмотрим простой пример того, как мы можемes6
середина箭头函数
Перевести вes5
серединаОбычная функция,который:
const sum=(a,b)=>a+b;
Как мы объединяем выше простоеsum
Стрелочные функции преобразуются в следующий вид:
const sum = function(a,b){
return a+b;
}
Подумайте об этом, какие у вас мысли?
Если вы не понимаетеAST
Если вы понимаете, это, несомненно, очень сложная проблема, и начать ее невозможно.AST
, это будет оченьeasy
пример.
Далее, давайте посмотрим, как этого добиться?
Установить зависимости
Требуются действияAST
Код, здесь нам нужно использовать две библиотеки, которые@babel/core
а такжеbabel-types
.
в@babel/core
даbabel
Основная библиотека, используемая для реализации механизма преобразования ядра,babel-types
Оценка типа, используемая для генерацииAST
Части
cd
в каталог, который вам нравится, черезnpm -y init
После операции инициализации перейдитеnpm i @babel/core babel-types -D
Установить зависимости
целевой анализ
Чтобы преобразовать, нам нужно проанализировать, сначала мы проходимastexplorerПосмотрите, как объектный код отличается от нашего текущего кода.
исходный кодAST
Структура выглядит следующим образом:
// 源代码的 AST
{
"type": "Program",
"start": 0,
"end": 21,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 21,
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 20,
"id": {
"type": "Identifier",
"start": 6,
"end": 9,
"name": "sum"
},
"init": {
"type": "ArrowFunctionExpression",
"start": 10,
"end": 20,
"id": null,
"expression": true,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 11,
"end": 12,
"name": "a"
},
{
"type": "Identifier",
"start": 13,
"end": 14,
"name": "b"
}
],
"body": {
"type": "BinaryExpression",
"start": 17,
"end": 20,
"left": {
"type": "Identifier",
"start": 17,
"end": 18,
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 19,
"end": 20,
"name": "b"
}
}
}
}
],
"kind": "const"
}
],
"sourceType": "module"
}
код объектаAST
Структура выглядит следующим образом:
// 目标代码的 `AST`
{
"type": "Program",
"start": 0,
"end": 48,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 48,
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 47,
"id": {
"type": "Identifier",
"start": 6,
"end": 9,
"name": "sum"
},
"init": {
"type": "FunctionExpression",
"start": 12,
"end": 47,
"id": null,
"expression": false,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 22,
"end": 23,
"name": "a"
},
{
"type": "Identifier",
"start": 25,
"end": 26,
"name": "b"
}
],
"body": {
"type": "BlockStatement",
"start": 28,
"end": 47,
"body": [
{
"type": "ReturnStatement",
"start": 32,
"end": 45,
"argument": {
"type": "BinaryExpression",
"start": 39,
"end": 44,
"left": {
"type": "Identifier",
"start": 39,
"end": 40,
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 43,
"end": 44,
"name": "b"
}
}
}
]
}
}
}
],
"kind": "const"
}
],
"sourceType": "module"
}
в этомstart
а такжеend
Нам все равно, это просто отметить расположение его кода.
Что нас волнует, так этоbody
Содержимое внутри, путем сравнения, мы обнаружили, что основное отличие заключается в том, чтоinit
Этот абзац одинArrowFunctionExpression
, другойFunctionExpression
, нам нужно толькоArrowFunctionExpression
Содержимое превратилось вFunctionExpression
Вот и все.
Мелкий измельчитель
мы строимarrow.js
файл, импортируйте две вышеуказанные библиотеки, а именно
//babel 核心库,用来实现核心转换引擎
const babel = require('@babel/core')
//类型判断,生成AST零部件
const types = require('babel-types')
//源代码
const code = `const sum=(a,b)=>a+b;` //目标代码 const sum = function(a,b){ return a + b }
Здесь нам нужно использоватьbabel
серединаtransform
метод, который можетjs
код вAST
, процесс может быть выполнен с использованиемplugins
правильноAST
преобразовать и, наконец, создать новыйAST
а такжеjs
Код, весь процесс использует более подходящую картинку в Интернете:
оbabel transform
Подробное использование, здесь не так много обсуждений, если вам интересно, вы можете перейти кОфициальный сайтпонять~
Его основное использование заключается в следующем:
//transform方法转换code
//babel先将代码转换成ast,然后进行遍历,最后输出code
let result = babel.transform(code,{
plugins:[
{
visitor
}
]
})
Ядром является плагинvisitor
реализация.
Это подключаемый объект, который может обрабатывать определенный тип узла, здесь узел, который нам нужно обработать,ArrowFunctionExpression
Существует две общие методы конфигурации:
Один представляет собой единый процесс, структура которого следующая, гдеpath
представляет текущий пройденный путьpath.node
узел, представляющий текущую переменную
let visitor = {
ArrowFunctionExpression(path){
}
}
Другой для двунаправленной обработки ввода и вывода, структура следующая, параметрыnode
Указывает текущий пройденный узел
let visitor = {
ArrowFunctionExpression : {
enter(node){
},
leave(node){
}
}
}
Здесь нам нужно обработать его только один раз, поэтому используется первый метод.
Анализируя объектный кодAST
, мы находим, чтоFunctionExpression
, то нам нужно использоватьbabel-types
, его роль заключается в том, чтобы помочь нам производить эти узлы
через егоnpm
упаковкаДокументацияпосмотреть, построитьFunctionExpression
Требуемые параметры следующие:
Ссылаться наAST
мы можем видеть егоid
дляnull
,params
это оригиналArrowFunctionExpression
серединаparams
,body
ЯвляетсяblockStatement
, мы также можем видетьbabel-types
документ, сt.blockStatement(body, directives)
Чтобы создать и так далее, следуйте за котом и рисуйте тигра, и конечный результат выглядит следующим образом:
//原 params
let params = path.node.params;
//创建一个blockStatement
let blockStatement = types.blockStatement([
types.returnStatement(types.binaryExpression(
'+',
types.identifier('a'),
types.identifier('b')
))
]);
//创建一个函数
let func = types.functionExpression(null, params, blockStatement, false, false);
наконец прошлоpath.replaceWith(func);
Просто замените его;
Код завершения выглядит следующим образом:
//babel 核心库,用来实现核心转换引擎
const babel = require('@babel/core')
//类型判断,生成AST零部件
const types = require('babel-types')
//源代码
const code = `const sum=(a,b)=>a+b;` //目标代码 const sum = function(a,b){ return a + b }
//插件对象,可以对特定类型的节点进行处理
let visitor = {
//代表处理 ArrowFunctionExpression 节点
ArrowFunctionExpression(path){
let params = path.node.params;
//创建一个blockStatement
let blockStatement = types.blockStatement([
types.returnStatement(types.binaryExpression(
'+',
types.identifier('a'),
types.identifier('b')
))
]);
//创建一个函数
let func = types.functionExpression(null, params, blockStatement, false, false);
//替换
path.replaceWith(func);
}
}
//transform方法转换code
//babel先将代码转换成ast,然后进行遍历,最后输出code
let result = babel.transform(code,{
plugins:[
{
visitor
}
]
})
console.log(result.code);
Выполните код, результаты печати будут следующими:
На этом преобразование нашей функции завершено и ожидаемый эффект достигнут.
Как это не удивительно! !
На самом деле это не так уж и сложно, главное проанализировать егоAST
Структура, пока мы понимаем, что нам нужно делать, тогда отпускаем и делаем это~
pass
: Внимательные студенты обнаружили, что приведенный выше код действительно можно оптимизировать, потому что нашreturnStatement
На самом деле, это также с исходным кодомreturnStatement
Он непротиворечив, нам нужно использовать его только для повторного использования, поэтому приведенный выше код также можно изменить на следующий:
let blockStatement = types.blockStatement([
types.returnStatement(path.node.body)
]);
куй железо пока горячо
Я только что научился использоватьAST
Для преобразования кода вы можете с тем же успехом ковать железо, пока горячо, и снова задавать следующий вопрос.
какes6
серединаclass
изменено вes5
изfunction
форма~
исходный код
// 源代码
class Person {
constructor(name) {
this.name=name;
}
sayName() {
return this.name;
}
}
код объекта
// 目标代码
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
С помощью приведенной выше основы просто следуйте за кошкой и нарисуйте тигра. Я не буду повторять это здесь. Процесс очень важен. Здесь я только вставляю основной код преобразования для справки.
ClassDeclaration (path) {
let node = path.node; //当前节点
let id = node.id; //节点id
let methods = node.body.body; // 方法数组
let constructorFunction = null; // 构造方法
let functions = []; // 目标方法
methods.forEach(method => {
//如果是构造方法
if ( method.kind === 'constructor' ) {
constructorFunction = types.functionDeclaration(id, method.params, method.body, false, false);
functions.push(constructorFunction)
} else {
//普通方法
let memberExpression = types.memberExpression(types.memberExpression(id, types.identifier('prototype'), false), method.key, false);
let functionExpression = types.functionExpression(null, method.params, method.body, false, false);
let assignmentExpression = types.assignmentExpression('=', memberExpression, functionExpression);
functions.push(types.expressionStatement(assignmentExpression));
}
})
//判断,replaceWithMultiple用于多重替换
if(functions.length === 1){
path.replaceWith(functions[0])
}else{
path.replaceWithMultiple(functions)
}
}
Суммировать
В нашей повседневной работе большую часть времени мы концентрируемся только наjs
сам код, не проходяAST
Чтобы заново понять и интерпретировать свой собственный код~
Эта статья также только черезAST
Некоторые вступления книги играют роль привлечения новых идей, позволяя иметь предварительное представление о ней, и она уже не такая незнакомая для вас.
Поиски и исследования кода бесконечны~
Вы можете построить из него все, что хотите, если хотитеjs
код~
Ну давай же!