Заявление об ограничении ответственности: я хочу поделиться своими знаниями о JavaScript, чтобы эксперты в этой области могли привести мое понимание ближе к истине. Поэтому, пожалуйста, отнеситесь к следующей информации с недоверием, а также, если вы заметите какие-либо ошибки, сообщите мне.
В этом посте я собираюсь исследовать процесс того, как движок JavaScript читает и выполняет ваш код.
Терминология:
Контекст выполнения: создается при вызове функции. Сам контекст выполнения можно рассматривать как объект JS, который отвечает за отслеживание следующей информации о вызванной функции: что вызвало функцию (this); каковы его аргументы, переменные и функции; и к каким аргументам, переменным и функциям он имеет доступ, что находится вне его (области действия).
Глобальный контекст выполнения: создается, когда мы вызываем наш исходный код.
Стек вызовов выполнения: стек отвечает за отслеживание контекста выполнения. Глобальный контекст выполнения всегда находится внизу. Каждый новый контекст выполнения добавляется в начало. Добавление каждого нового контекста выполнения в стек приводит к тому, что механизм JS приостанавливает выполнение в текущем контексте, перемещается во вновь добавленный контекст и запускает процесс выполнения; после завершения JS-движок возвращается к своему предыдущему контексту и возобновляет работу с того места, где он оставался.
Цепочка областей видимости: список аргументов, переменных и функций, доступных для вызываемой функции из внешней среды.
VariableObject: можно рассматривать как объект, который отслеживает аргументы, функции и переменные вызываемой функции. Все аргументы, функции и переменные являются свойствами VariableObject.
Это: связано с объектом, который вызвал функцию.
Объяснение компиляции и выполнения:
Я считаю, что лучший способ понять, как работает JS-движок, - это проработать пример. Давайте рассмотрим следующий фрагмент кода, как если бы мы являлись движком JS, работающим в веб-браузере.
Когда следующий код выполняется веб-браузером, по умолчанию глобальный контекст выполнения добавляется в стек выполнения.
После добавления контекста в стек движок JS выполняет следующие два основных шага:
Этап создания:
- Создайте цепочку областей видимости
- Создайте объект VariableObject
a. Определите аргументы и присвойте им имена как свойства VariableObject, и значения аргументов станут значением свойств.
b. Просканируйте и определите объявление функции и назначьте их как свойства VariableObject и сделайте ссылку на свойство в объявлении функции
c. Просканируйте и определите любые переменные (включая функциональные выражения). Если имя переменной совпадает с именем ранее определенного свойства, пропустите переменную полностью и перейдите к следующей переменной. Если переменная не существует, сделайте ее свойством VariableObject и присвойте ей значение undefined (также проверьте, как let и const оцениваются движком JS ) *. - Определите ценность этого
Этап активации / выполнения:
- Начните выполнение кода. Когда во время выполнения обнаруживается переменная, присвойте ей значение. Если значение является функцией (подумайте о выражении функции), присвойте ссылку на функцию переменной.
Мы применим следующие шаги к нашему глобальному контексту выполнения:
Переход к стадии создания (глобальный контекст выполнения):
- Создайте цепочку областей видимости
- Создать VariableObject
- this: глобальный
Войдите в стадию активации / выполнения (глобальный контекст выполнения):
Начните выполнение кода сверху вниз.
- В первой строке мы встречаем переменную addNum, для которой VariableObject определено как «undefined». Передаем ему ссылку на функцию суммы. Теперь наш VariableObject для глобального контекста выполнения готов,
- Во второй строке мы встречаем объявление функции, которое обрабатывается на этапе создания. Мы можем пропустить это и перейти к следующей строке
- Третья строка, функция addNum вызывается путем передачи аргументов 2 и 3. Согласно нашему определению содержимого выполнения, новый контекст выполнения создается после вызова функции. И контекст добавлен в наш стек выполнения. Теперь мы переходим к выполнению функции, следуя тем же шагам, что и для глобального контекста выполнения.
Этап создания (контекст выполнения функции addNum):
- Создайте цепочку областей видимости
- Создать VariableObject
- this: global // причина, по которой this равно global, в том, что глобальная среда отвечает за вызов функции. И это связано с объектом, который вызвал функцию.
Этап активации / выполнения (контекст выполнения функции addNum):
И снова мы начинаем сверху и продвигаемся вниз.
- В первой строке мы встречаем console.log, который представляет собой выражение, которое оценивает значение 5 для консоли.
- Завершите выполнение функции, удалите контекст из стека и вернитесь в глобальный контекст выполнения с того места, где мы остановились.
И мы повторяем описанный выше шаг для вызова функции minusNum.
Завершение выполнения minusNum приведет нас к завершению нашего глобального контекста выполнения, поскольку больше нет операторов.
И глобальный контекст выполнения выталкивается из стека выполнения, и программа завершается.
Наконец, я оставлю вам фрагмент кода, чтобы проверить ваше понимание концепции. Свое объяснение вы можете отправить в комментариях.
Подробнее:
- Дэвид Шарифф дает отличный обзор процесса выполнения JS
- DailyJS также имеет отличную статью о контексте выполнения
- Еще один ресурс для лучшего понимания контекста выполнения от SimpleProgrammer
- Дмитрий Сошников разбирает внутреннюю работу спецификации EMCAScript
- Кайл Симпсон подробно рассказывает о том, как работает JS-движок
- И сама спецификация ECMAScript
Обновления:
- * Июль 2017 г .: Мое понимание того, что ранее определенные переменные пропускаются на этапе создания, является ложным, согласно моим выводам и другим разработчикам. Дополнительную информацию можно найти в следующем вопросе stackoverflow: Почему объявления переменных всегда могут перезаписывать объявления функций?