let jsCartoons = «Потрясающе»;
Обзор
В предыдущей статье мы подробно рассказали, как движок JavaScript работает с точки зрения выполнения событий, и кратко упомянули компиляцию. Да, вы прочитали это правильно. JavaScript компилируется, хотя в отличие от компиляторов других языков, у которых есть этапы сборки, допускающие раннюю оптимизацию, компиляторы JavaScript вынуждены компилировать код в последнюю секунду - буквально. Технология, используемая для компиляции JavaScript, удачно названа Just-In-Time (JIT). Эта компиляция на лету появилась в современных движках JavaScript для ускорения браузеров, которые их реализуют.
Когда разработчики называют JavaScript интерпретируемым языком, это может немного сбить с толку. Это потому, что движки JavaScript до недавнего времени всегда были связаны с интерпретатором. Теперь, с такими движками, как движок Google V8, разработчики могут съесть свой пирог и съесть его - движок может иметь как интерпретатор, так и компилятор.
Мы собираемся показать вам, как код JavaScript обрабатывается с помощью одного из этих новомодных JIT-компиляторов. Мы не собираемся вам показывать сложные механизмы, с помощью которых эти новые движки JavaScript оптимизируют код. Эти механизмы включают такие методы, как встраивание (удаление пробелов), использование скрытых классов и устранение избыточности. Вместо этого в этой статье будут рассмотрены общие концепции теории компиляции, чтобы дать вам представление о том, как современные движки JavaScript работают внутри.
Отказ от ответственности: вы можете стать веганом.
Язык и код
Чтобы понять, как компилятор читает код, полезно подумать о языке, который вы используете для чтения этой статьи: английском. Мы все сталкивались с ярко-красным SyntaxError
в наших консолях для разработки, но, когда мы почесали голову в поисках отсутствующей точки с запятой, мы, вероятно, никогда не останавливались, чтобы думать о Ноаме Хомски. Хомский определяет синтаксис как:
«Изучение принципов и процессов, с помощью которых строятся предложения в определенных языках».
Мы вызовем нашу «встроенную» simplify()
функцию по определению Ноама Хомского.
simplify(quote, "grossly")
//Result: Languages order their words differently.
Конечно, Хомский имел в виду такие языки, как немецкий и суахили, а не JavaScript и Ruby. Тем не менее, языки программирования высокого уровня созданы по образцу языков, на которых мы говорим. По сути, компиляторы JavaScript были «научены» читать JavaScript опытными инженерами, точно так же, как наши родители и учителя приучили наш мозг читать предложения.
В отношении компиляторов мы можем наблюдать три области лингвистического исследования: лексические единицы, синтаксис и семантика. Другими словами, изучение значения слов и их отношений, изучение расположения слов и изучение значений предложений (мы ограничили определение семантики в соответствии с нашей целью).
Возьмите это предложение: Мы ели говядину.
лексическая единица
Обратите внимание, как каждое слово в предложении можно разбить на единицы лексического значения: We / ate / beef
синтаксис
Это базовое предложение синтаксически следует соглашению Субъект / Глагол / Объект. Предположим, что так должно быть построено каждое английское предложение. Почему? Потому что компиляторы должны работать в соответствии со строгими правилами, чтобы обнаруживать синтаксические ошибки. Итак, Говядина, которую мы ели, хотя и понятно , будет неверным в нашем упрощенном английском.
семантика
Семантически предложение имеет собственное значение. Мы знаем, что раньше говядину ели несколько человек. Мы можем лишить его смысла, переписав предложение как: Мы + говядина съели.
Теперь давайте переведем наше оригинальное английское предложение в выражение на JavaScript.
let sentence = “We ate beef”;
лексическая единица
Выражения можно разбить на лексемы: let / предложение / = / «Мы ели говядину» /;
синтаксис
Наше выражение, как и предложение, должно быть синтаксическим. JavaScript, как и большинство других языков программирования, следует порядку (Тип) / Переменная / Назначение / Значение. Тип применим в зависимости от контекста. Если вас, как и нас, беспокоит неплотность объявления типа, вы можете просто добавить “use strict”;
в глобальную область видимости своей программы. “use strict”;
- властный грамматист, который применяет синтаксис JavaScript. Преимущества его использования перевешивают неудобства. Доверься нам.
семантика
Семантически наш код имеет значение, которое наши машины в конечном итоге поймут через компилятор. Чтобы получить семантическое значение из кода, компилятор должен читать код. Мы поговорим об этом в следующем разделе.
Примечание. Контекст отличается от области действия. Дальнейшее объяснение выходит за рамки этой статьи.
LHS / RHS
Мы читаем по-английски слева направо, в то время как компилятор читает код в обоих направлениях. Как? С поиском слева (LHS) и поиском справа (RHS). Давайте разберем их.
Фокус поиска LHS - это «левая сторона» задания. На самом деле это означает, что он отвечает за цель задания. Мы должны концептуализировать цель, а не позицию, потому что цель поиска LHS может варьироваться по своему положению. Кроме того, присваивание не ссылается явно на оператор присваивания.
Ознакомьтесь с приведенным ниже примером для пояснения:
function square(a){ return a*a; } square(5);
Вызов функции запускает поиск LHS для a
. Почему? Поскольку передача 5
в качестве аргумента неявно присваивает значение a. Обратите внимание, что цель не может быть определена путем позиционирования с первого взгляда и должна быть выведена.
И наоборот, поиск в RHS фокусируется на самих ценностях. Итак, если мы вернемся к нашему предыдущему примеру, поиск RHS найдет значение a в выражении a*a;
Важно помнить, что эти поиски происходят на последней фазе компиляции, фазе генерации кода. Мы более подробно остановимся на этом, когда дойдем до этого этапа. А пока давайте исследуем компилятор.
Компилятор
Думайте о компиляторе как о мясоперерабатывающем предприятии с несколькими механизмами, которые превращают код в пакет, который наш компьютер считает съедобным или исполняемым. В этом примере мы будем обрабатывать Expression.
Токенизатор
Сначала токенизатор разбивает код на единицы, называемые токенами.
Эти токены затем идентифицируются токенизатором. Лексическая ошибка возникает, когда токенизатор находит «алфавит», не принадлежащий языку. Помните, это отличается от синтаксической ошибки. Например, если бы мы использовали символ @ вместо оператора присваивания, токенизатор увидел бы этот символ @ и сказал бы: «Хммм… Этой лексемы нет в лексиконе JavaScript… ЗАКРЫТЬ ВСЕ. КОД КРАСНЫЙ."
Примечание. Если эта же система может создавать ассоциации между одним токеном и другим токеном, а затем группировать их вместе как парсер, она будет считаться лексером.
Парсер
Парсер ищет синтаксические ошибки. Если ошибок нет, он упаковывает токены в структуру данных, называемую деревом синтаксического анализа. На этом этапе процесса компиляции код JavaScript считается проанализированным, а затем подвергается семантическому анализу. Еще раз, если следовать правилам JavaScript, создается новая структура данных, называемая абстрактным синтаксическим деревом (AST).
Существует промежуточный шаг, на котором исходный код преобразуется в промежуточный код - обычно байт-код - интерпретатором, инструкция за инструкцией. Затем байт-код выполняется на виртуальной машине.
После этого код оптимизируется. Это включает в себя удаление пробелов, мертвого кода и избыточного кода, среди многих других процессов оптимизации.
Генератор кода
После того, как код оптимизирован, задача генератора кода состоит в том, чтобы взять промежуточный код и превратить его в язык ассемблера низкого уровня, который машина может легко понять. На данном этапе генератор отвечает за:
(1) убедиться, что код низкого уровня сохраняет те же инструкции, что и исходный код.
(2) отображение байт-кода на целевую машину
(3) принятие решения о том, должны ли значения храниться в регистре или в памяти и где значения должны быть получены.
Здесь генератор кода выполняет поиск LHS и RHS. Проще говоря, поиск LHS записывает в память значение цели, а поиск RHS считывает значение из памяти.
Если значение хранится как в кэше, так и в регистре, генератор должен будет оптимизировать, взяв значение из регистра. Получение значений из памяти должно быть наименее предпочтительным методом.
И наконец…
(4) определение порядка выполнения инструкций.
Последние мысли
Еще один способ понять механизм JavaScript - посмотреть на свой мозг. Пока вы это читаете, ваш мозг получает данные с сетчатки. Эти данные, передаваемые вашим зрительным нервом, представляют собой перевернутую версию этой веб-страницы. Ваш мозг компилирует изображение, переворачивая его так, чтобы его можно было интерпретировать.
Помимо простого переворачивания изображений и их раскрашивания, ваш мозг может заполнять пустые места на основе своей способности распознавать закономерности, например, способности компилятора считывать значения из кэшированной памяти.
Итак, если мы напишем, пожалуйста, дайте нам раунд ______, вы легко сможете выполнить этот код.
код с миром
Раджи Айнла,
Стажер, автор технического контента @ Codesmith Staffing
Ресурсы