что такое АСТ
Абстрактное синтаксическое дерево, или сокращенно 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;
Наконец
Если у вас есть время, вы также можете попробовать некоторые распространенные преобразования кода, такие как стрелочные функции в обычные функции, после того, как закончите попытки, что может усилить ваше впечатление.
Во всей статье, если есть какие-либо ошибки или неточности, обязательно исправьте их, спасибо!
Ссылаться на