что такое АСТ
Абстрактное синтаксическое дерево, или сокращенно AST, представляет собой абстрактное представление грамматической структуры исходного кода. Он представляет синтаксическую структуру языка программирования в виде дерева, и каждый узел в дереве представляет собой структуру в исходном коде.
В чем польза АСТ
AST широко используется, например:
- Подсказки редактора об ошибках, форматирование кода, подсветка кода, автодополнение кода;
-
elint
,pretiier
проверка кода на наличие ошибок или стиля; -
webpack
пройти черезbabel
перевестиjavascript
грамматика;
И если вы хотите понять, как компилируется и выполняется js, вам нужно понимать AST.
Как генерируется АСТ
Первым шагом, выполняемым js, является чтение потока символов в файле js, а затем его генерация с помощью лексического анализа.token
, а затем сгенерировать AST через синтаксический анализатор и, наконец, сгенерировать машинный код для выполнения.
Весь процесс анализа в основном делится на следующие два этапа:
- Сегментация слов: разбивает всю строку кода на массив минимальных синтаксических единиц.
- Синтаксический анализ: установление и анализ отношений между грамматическими единицами на основе сегментации слов.
JS Parser — это парсер js, который может преобразовывать исходный код js в AST. Общие парсеры включают esprima, traceur, acorn, shift и т. д.
лексический анализ
Лексический анализ, также известный как сканер, просто вызывает метод next(), считывает символы один за другим, а затем сравнивает их с определенными ключевыми символами JavaScript для создания соответствующего токена. Токен представляет собой неделимую наименьшую единицу:
Например, три символа var могут использоваться только целиком и не могут быть разделены семантически, поэтому это токен.
В лексическом анализаторе каждое ключевое слово является Токеном, каждый идентификатор — Токеном, каждый оператор — Токеном, и каждый знак препинания — тоже Токеном. В дополнение к этому отфильтровываются комментарии и пробельные символы (переводы строк, пробелы, табуляции и т. д.) в исходной программе.
В конце концов, весь код будет разбит на список токенов (или одномерный массив).
Разбор
Синтаксический анализ преобразует токен из лексического анализа в структуру абстрактного синтаксического дерева с грамматическим значением. В то же время проверяется синтаксис, и если синтаксис неверен, выдается синтаксическая ошибка.
Сказав так много, давайте посмотрим, как выглядит фрагмент кода JavaScript после его преобразования в AST, и покажем это с помощью простой строки кода.
🌰 Пример 1
const fn = a => a;
Из этого синтаксического дерева AST мы можем ясно видеть конкретное значение кода, а также используемый синтаксис, методы и т. д.
Если перевести эту диаграмму человеческим языком, то: объявите переменную fn типа const, чтобы она указывала на выражение стрелочной функции, параметром которого является а, а тело функции также имеет значение а.
🌰 Пример 2
const fn = a => {
let i = 1;
return a + i;
};
Смотрим на корпус:
🌰 Пример 3
вызов функции
function test(){
let a = 1;
console.log(a)
}
В основном см.MemberExpression
Все приведенные выше скриншоты обработаны с помощью Acorn. Причина использования Acorn в том, что насколько я понимаю, Acorn признан самым быстрым в парсинге. Инструмент упаковки Webpack, который мы используем, также использует Acorn для Babel.
Свойства на снимке экрана выше являются частью AST, и эта структура содержит множество свойств.
- Объявление переменной VariableDeclaration
- Описание объявления переменной VariableDeclarator
- Узел выражения выражения
- …
Больше отображения недвижимости:
- можешь идтиAST explorerВы можете увидеть AST, полученный разными парсерами онлайн после парсинга кода js.
- Посмотреть все ESTrees на githubESTree
- Документация по внедрению свойствВведение в абстрактное синтаксическое дерево AST
Применение реальных боевых АСТ
тема
через вышеуказанноеconsole.log
АСТ, давайте завершим звонокconsole.log(xx)
При добавлении имени функции на передний план пользователь может легко увидеть, какая функция вызывается при печати.
举例
// 源代码
function getData() {
console.log("data")
}
// --------------------
// 转化后代码
function getData() {
console.log("getData", "data");
}
вводить
Во-первых, давайте представим инструменты, которые нам нужно использоватьBabel
-
@babel/parser
: поставить код js -------->>>AST
абстрактное синтаксическое дерево; -
@babel/traverse
правильноAST
Узлы просматриваются рекурсивно; -
@babel/types
к конкретномуAST
Узел изменен; -
@babel/generator
:AST
Абстрактное дерево синтаксиса ------->>> новый код js;
Зачем использовать babel?В основном потому, что им проще пользоваться (знаком только с этим 😭).
Входить@babel/parserВ начале официального сайта сообщается, что он использует Acorn для разбора js-кода в синтаксическое дерево AST (что указывает на то, что Acorn действительно лучше).
начать кодирование
- Создайте новый файл, чтобы открыть консоль для установки необходимых пакетов.
cnpm i @babel/parser @babel/traverse @babel/types @babel/generator -D
- Создайте файл js и напишите примерный макет следующим образом. Используйте AST
const generator = require("@babel/generator");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse");
const types = require("@babel/types");
function compile(code) {
// 1.parse 将代码解析为抽象语法树(AST)
const ast = parser.parse(code);
// 2,traverse 转换代码
traverse.default(ast, {});
// 3. generator 将 AST 转回成代码
return generator.default(ast, {}, code);
}
const code = `
function getData() {
console.log("data")
}
`;
const newCode = compile(code)
Используйте узел для запуска результата, потому что ничего не обрабатывается, на выходе исходный код,
Улучшить метод компиляции
function compile(code) {
// 1.parse
const ast = parser.parse(code);
// 2,traverse
const visitor = {
CallExpression(path) {
// 拿到 callee 数据
const { callee } = path.node;
// 判断是否是调用了 console.log 方法
// 1. 判断是否是成员表达式节点,上面截图有详细介绍
// 2. 判断是否是 console 对象
// 3. 判断对象的属性是否是 log
const isConsoleLog =
types.isMemberExpression(callee) &&
callee.object.name === "console" &&
callee.property.name === "log";
if (isConsoleLog) {
// 如果是 console.log 的调用 找到上一个父节点是函数
const funcPath = path.findParent(p => {
return p.isFunctionDeclaration();
});
// 取函数的名称
const funcName = funcPath.node.id.name;
// 将名称通过 types 来放到函数的参数前面去
path.node.arguments.unshift(types.stringLiteral(funcName));
}
}
};
// traverse 转换代码
traverse.default(ast, visitor);
// 3. generator 将 AST 转回成代码
return generator.default(ast, {}, code);
}
Чистый код выглядит трудным для понимания.Ниже приведен формат данных, который я записал вышеприведенным path.node в файл для всеобщего обозрения.
{
"type": "CallExpression",
"start": 24,
"end": 43,
"loc": {
"start": { "line": 3, "column": 2 },
"end": { "line": 3, "column": 21 }
},
"callee": {
"type": "MemberExpression",
"start": 24,
"end": 35,
"loc": {
"start": { "line": 3, "column": 2 },
"end": { "line": 3, "column": 13 }
},
"object": {
"type": "Identifier",
"start": 24,
"end": 31,
"loc": {
"start": { "line": 3, "column": 2 },
"end": { "line": 3, "column": 9 },
"identifierName": "console"
},
"name": "console"
},
"property": {
"type": "Identifier",
"start": 32,
"end": 35,
"loc": {
"start": { "line": 3, "column": 10 },
"end": { "line": 3, "column": 13 },
"identifierName": "log"
},
"name": "log"
},
"computed": false
},
"arguments": [
{
"type": "StringLiteral",
"start": 36,
"end": 42,
"loc": {
"start": { "line": 3, "column": 14 },
"end": { "line": 3, "column": 20 }
},
"extra": { "rawValue": "data", "raw": "'data'" },
"value": "data"
}
]
}
Мы удаляем ненужные атрибуты информации о местоположении (start, end, loc), и код будет понятен с первого взгляда при сравнении данных.
снова запустить файл
Отлично, вызовите метод console.log с именем функции, добавленным перед параметром метода, готово! !
Для удобства всех ниже приведен полный код
const generator = require("@babel/generator");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse");
const types = require("@babel/types");
const fs = require("fs");
function compile(code) {
// 1.parse
const ast = parser.parse(code);
// 2,traverse
const visitor = {
CallExpression(path) {
const { callee } = path.node;
const isConsoleLog =
types.isMemberExpression(callee) &&
callee.object.name === "console" &&
callee.property.name === "log";
if (isConsoleLog) {
const funcPath = path.findParent(p => {
return p.isFunctionDeclaration();
});
const funcName = funcPath.node.id.name;
fs.writeFileSync("./funcPath.json", JSON.stringify(funcPath.node), err => {
if (err) throw err;
console.log("写入成功");
});
path.node.arguments.unshift(types.stringLiteral(funcName));
}
}
};
traverse.default(ast, visitor);
// 3. generator
return generator.default(ast, {}, code);
}
const code = `
function getData() {
console.log('data')
}
`;
console.log(compile(code).code);
Видя это, если вы думаете, что проблем нет, я полагаю, что у вас есть четкое понимание AST и определенное понимание скомпилированного кода babel, поэтому вы не будете так незнакомы с babel, когда будете писать конфигурацию веб-пакета в будущем.
Суммировать
Чтобы быть совместимым с браузерами более ранних версий, мы обычно используем webpack для упаковки и компиляции нашего кода, чтобы сократить синтаксис ES6, такой как функции стрелок, до обычных функций. Изменение объявлений const и let на var и т. д. выполняется через AST, но процесс реализации более сложен и утончен. Но это все три:
- js грамматика разбирается в AST;
- Изменить АСТ;
- Преобразование AST в синтаксис js;
Наконец
Если у вас есть время, вы также можете попробовать некоторые распространенные преобразования кода, такие как стрелочные функции в обычные функции, после того, как закончите попытки, что может усилить ваше впечатление.
Во всей статье, если есть какие-либо ошибки или неточности, обязательно исправьте их, спасибо!
Ссылаться на