Поговорим об AST в Javascript

JavaScript внешний фреймворк

Что такое абстрактное синтаксическое дерево (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Код, весь процесс использует более подходящую картинку в Интернете:

transform

о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Требуемые параметры следующие:

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код~

Ну давай же!