JS Rammed выполнение контекста и лексическая среда

JavaScript

предисловие

JavaScript — это интерпретируемый динамический язык, но в то же время это язык, полный тайн. Если вы хотите быть хорошим разработчиком JS, вам нужно понимать внутреннюю работу программ JavaScript.

Эта статья, основанная на восьмой главе последней спецификации ECMA, разъясняет лексическое окружение и контекст выполнения JavaScript. Это основа для понимания других концепций JavaScript (переходная мертвая зона let/const, подъем переменных, замыкания и т. д.).

Эта статья относится к последнему выпуску стандарта ECMA-262 десятого поколения, а именно ES2019, щелкните официальныйадрес документа. ES2019 иES6Содержание лексического окружения и контекст выполнения схожи, и ES2019 сделал некоторые дополнения в деталях, поэтому эта статья напрямую принимает стандарт ES2019. Вы также можете сравнить различия между двумя версиями стандарта.

Контекст выполнения

Контекст выполнения — это абстракция, используемая для отслеживания среды выполнения вашего кода. Каждый запуск кода создает по крайней мере один контекст выполнения. Код запускается в контексте выполнения.

Вы можете сравнить взаимосвязь между выполнением кода и контекстом выполнения с взаимосвязью между процессом и памятью.Информация о переменной среде во время выполнения кода помещается в контекст выполнения.При выполнении кода контекст выполнения также будет уничтожен.

Информация о состоянии во время выполнения кода записывается в контексте выполнения.В соответствии с различными сценариями выполнения контекст выполнения подразделяется на следующие типы:

  • Глобальный контекст выполнения: когда исполняемый код находится в глобальной области видимости, будет сгенерирован глобальный контекст выполнения, который также является самым основным контекстом выполнения программы.
  • Контекст выполнения функции: при вызове функции вызов функции создаст новый контекст выполнения.
  • Контекст выполнения eval: при выполнении функции eval будет сгенерирован собственный контекст.Так как eval используется редко, он не будет обсуждаться.

стек выполнения

С контекстом выполнения должны быть инструменты для правильного управления им. в то время как стек выполнения (Execution Context Stack) — это структура данных, используемая для управления всеми контекстами выполнения, созданными во время выполнения.Это стек LIFO (последний пришел, первый вышел), который также является стеком вызовов во время выполнения процесса известной JS-программы. Когда программа запустится, она сначала создаст глобальный контекст выполнения и поместит его в стек выполнения, а затем при каждом вызове функции будет создаваться и помещаться в стек новый контекст выполнения функции.

Давайте посмотрим на рабочий процесс стека выполнения из небольшого фрагмента кода:

<script>
    console.log('script')
    function foo(){
        function bar(){
            console.log('bar', isNaN(undefined))
        }
        bar()
        console.log('foo')
    }
    foo()
</script>

Когда эта JS-программа запускается, она создает глобальный контекст выполнения.GlobalContext, который инициализирует некоторые глобальные объекты или глобальные функции, например, в кодеconsole,undefined,isNaN. Поместите глобальный контекст выполнения в стек выполнения, обычно в движке JS есть указательrunningУкажите на верхний элемент стека:

Механизм JS будет обрабатывать функции, объявленные в глобальной области видимости (foo) инициализируется в глобальном контексте, а затем запускает построчный код выполнения, работающий доconsoleвrunningглобальный объект, находящийся в лексическом окружении в контексте, на который указываетconsoleи позвониlogфункция.

PS: Конечно, при звонкеlogПри вызове функции также необходимо создать новый контекст функции и поместить его в стек вызовов. Для простоты игнорируйтеlogПроцесс создания контекста.

бежать кfoo()Когда он распознается как вызов функции, в это время создается новый контекст выполнения.FooContextв стек,FooContextВнешняя ссылка внутреннего лексического окружения указывает на лексическое окружение глобального контекста выполнения, перемещаясьrunningУказатель на этот новый контекст:

по окончанииFooContextПосле создания перейдите вFooContextпродолжить выполнение кода вbar()Точно так же вам все еще нужно создать новый контекст выполненияBarContext,В настоящее времяBarContextВнешняя ссылка внутреннего лексического окружения будет указывать наFooContextЛексическое окружение:

продолжать работатьbarфункция, так как контекст функции имеетouterСсылки реализуют прогрессивные ссылки, поэтому вbarвсе еще можно получить внутри функцииconsoleвозражать и звонитьlog.

После этого закончитьbarа такжеfooПри вызове функции контекст будет извлекаться из стека по очереди, пока глобальный контекст не будет извлечен из стека и программа не завершится.

Создание контекста выполнения

Создание контекста выполнения делает две вещи:

  1. Создайте лексическую средуLexicalEnvironment;
  2. Создать переменную средуVariableEnvironment;

Таким образом, контекст выполнения должен концептуально выглядеть так:

ExecutionContext = {
  LexicalEnvironment = <ref. to LexicalEnvironment in memory>,
  VariableEnvironment = <ref. to VariableEnvironment in  memory>,
}

В глобальном контексте выполнения это указывает на глобальный объект,window in browser / global in nodejs.

Лексическая среда

Лексические среды — это тип спецификации в ECMA — лексические вложенные структуры на основе кода, используемые для записи ассоциации идентификаторов с определенными переменными или функциями. Проще говоря, лексическое окружение установленоидентификатор - переменнаятаблица сопоставления. Идентификатор здесь относится к имени переменной или имени функции, а переменная является фактическим исходным значением переменной или ссылочным адресом объекта/функции.

существуетLexicalEnvironmentОн состоит из трех частей:

  • Экологические записиEnvironmentRecord: место для хранения объявлений переменных и функций;
  • внешняя ссылкаouter: предоставляет ссылку для доступа к родительскому лексическому окружению, может быть пустым;
  • эта привязкаThisBinding: определить точку этого в текущей среде;

Типы лексических окружений

  • глобальная среда (GlobalEnvironment): в начале выполнения кода JavaScript хост (браузер, NodeJs и т. д.) заранее инициализирует глобальную среду.EnvironmentRecordсвяжет встроенный глобальный объект (Infinityи т.д.) или глобальная функция (eval,parseIntИ т. Д.), Глобальные переменные или функции Другие утверждения будут храниться в глобальной лексической среде. Глобальная средаouterцитируется какnull.

Упомянутые здесь глобальные объекты включают в себя все знакомые нам встроенные объекты, такие как конструкторы, такие как Math, Object и Array, а также глобальные переменные, такие как Infinity. К глобальным функциям относятся такие функции, как eval и parseInt.

  • среда модуля (ModuleEnvironment): Если вы писали программы NodeJs, вы будете знакомы с этой средой.В среде модуля вы можете прочитатьexport,moduleДругие переменные записываются в среде ER модуля. Среда модуляouterОриентир указывает на глобальную среду.

  • функциональное окружение (FunctionEnvironment): среда функции будет генерироваться каждый раз, когда вызывается функция, и среда функции будет включатьthisобязательный илиsuperвызов. Функция также будет записана в ERlengthа такжеargumentsАтрибуты. функциональная средаouterСсылка на родительскую среду, вызвавшую функцию. Переменные или функции, объявленные в теле функции, записываются в окружение функции.

Экологический рекорд ER

Переменные и функции, объявленные в коде, хранятся вEnvironmentRecordДоступ в ожидании выполнения. Экологические записиEnvironmentRecordЕсть также два разных типа, а именноdeclarativeа такжеobject.declarativeЯвляется более распространенным типом, обычно объявления функций, объявления переменных будут генерировать этот тип ER.objectтип может бытьwithоператор запущен, в то время какwithСценариев использования немного, и он редко используется общими разработчиками.

Если вы столкнулись с чем-то вродеvar const let class module import 函数声明, то экологический рекорд равенdeclarativeТип.

Стоит отметить, что ** глобальный контекстER** немного особенный, потому что онobject ERа такжеdeclarative ERсмесь из. существуетobject ERХранится в функции глобального объекта, объявлении функции функции,async,generator,var关键词变量。 существуетdeclarative ERхранить переменные, объявленные другими способами, напримерlet const classЖдать. Поскольку стандартный генерал-лейтенантobjectER типа считается базовым ER, поэтому здесь мы по-прежнему рассматриваем тип глобального ER какobject.

GlobalExecutionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            type: 'object',  // 混合 object + declarative
            NaN,
            parseInt,
            Object,
            myFunc,
            a,
            b,
            ...
        },
        outer: null,
        this: <globalObject>
    }
}

LexicalEnvironmentхранить только объявления функций иlet/constобъявленные переменные, такие же, как следующиеVariableEnvironmentСделать разницу.

Например, у нас есть следующий код:

let a = 10;
function foo(){
    let b = 20
    console.log(a, b)
}
foo()

// 它们的词法环境伪码如下:
GlobalEnvironment: {
    EnvironmentRecord: {
        type: 'object',
        a: <uninitialized>,
        foo: <func>
    },
    outer: <null>,
    this: <globalObject>
}

FunctionEnvironment: {
    EnvironmentRecord: {
        type: 'declarative',
        arguments: {length: 0},
        b: <uninitialized>,
    },
    outer: <GlobalEnvironment>,
    this: <globalObject>  // 严格模式下为undefined
}

запись среды функции

Поскольку функциональная среда является наиболее часто используемой лексической средой в нашем ежедневном процессе разработки, нам необходимо более глубоко изучить механизм работы функциональной среды, чтобы помочь нам лучше понять некоторые особенности языка.

Когда мы вызываем функцию, будет сгенерирован контекст выполнения функции.Запись окружения лексического окружения контекста выполнения функции является типом функции.

FunctionContext
    |LexicalEnvironment
        |EnvironmentRecord  //--> 函数类型

Почему акцент на этом типе? Поскольку ECMA записывает дополнительные внутренние свойства для функциональных сред:

Внутренние свойства Value иллюстрировать Пополнить
[[ThisValue]] Any Функциональный вызовthisГоворя об адресе, мы часто говорим, что функцияthisСвязывание заключается в присвоении значения этому внутреннему свойству.
[[ThisBindingStatus]] "lexical" / "initialized" / "uninitialized" если равноlexical, то это стрелочная функция, что означаетthisпусто; силаnewСтрелочные функции выдают ошибкуTypeErrorошибка
FunctionObject Object В этом объекте есть два свойства[[Call]]а также[[Construct]], все они являются функциями, способ выполнения присваивания зависит от того, как вызывается функция нормальное назначение вызова функции[[Call]], а черезnewилиsuperВызов функции присваивает значение[[Construct]]
[[HomeObject]] Object / undefined Если функция (не стрелочная функция) имеетsuperсвойство (подкласс), затем[[HomeObject]]указать на конструктор родительского класса если бы ты написалextendsзнаю, о чем я говорю
[[NewTarget]] Object / undefined если через[[Construct]]метод для вызова функции, затем[[NewTarget]]не пустой можно передать в функциюnew.targetПрочтите это внутреннее свойство. Это нужно для того, чтобы определить, проходит ли функцияnewзвонить

Кроме того, в записи среды функции есть объект arguments, который записывает информацию о входных параметрах функции.

ThisBinding

Эта привязка - старомодный вопрос, из-за наличия различных сценариев анализа, здесь неудобно расширяться. 👉«Четыре принципа ThisBinding консолидации JS»Цель этой привязки — указать точку this при создании контекста выполнения и прочитать правильный объект, на который ссылается this, во время выполнения функции.

резюме

Существует слишком много типов понятий, и некоторые из них беспорядочны. Просто краткое примечание:

词法环境分类 = 全局 / 函数 / 模块
词法环境 = ER + outer + this
ER分类 = declarative(DER) + object(OER)
全局ER = DER + OER

переменная среда VariableEnvironment

До ES6 объявление переменных выполнялось черезvarОбъявления ключевых слов поддерживаются в ES6letа такжеconstобъявлять переменные, для совместимостиvarСпособ написания, поэтому используйте переменную среду для храненияvarобъявленная переменная.

varКлючевые слова имеют характеристику, которая делаетпеременное продвижение, а черезlet/constОбъявленные переменные не поднимаются. Чтобы различить эти два случая, для их различения используются разные лексические среды.

Окружение переменных по сути остается лексическим окружением, но оно только хранитvarПеременная объявлена ​​так, что при инициализации переменной ей может быть присвоено значениеundefined.

С учетом этих концепций как должен выглядеть полный контекст выполнения? Вот несколько примеров 🌰:

let a = 10;
const b = 20;
var sum;

function add(e, f){
    var d = 40;
    return d + e + f 
}

let utils = {
    add
}

sum = utils.add(a, b)

Полный контекст выполнения выглядит так:

GlobalExecutionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            type: 'object',
            add: <function>,
            a: <uninitialized>,
            b: <uninitialized>,
            utils: <uninitialized>,
        },
        outer: null,
        this: <globalObject>
    },
    VariableEnvironment: {
        EnvironmentRecord: {
            type: 'object',
            sum: undefined
        },
        outer: null,
        this: <globalObject>
    },
}

// 当运行到函数add时才会创建函数执行上下文
FunctionExecutionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            type: 'declarative',
            arguments: {0: 10, 1: 20, length: 2},
            [[ThisValue]]: <utils>,
            [[NewTarget]]: undefined,
            ...
        },
        outer: <GlobalLexicalEnvironment>,
        this: <utils>
    },
    VariableEnvironment: {
        EnvironmentRecord: {
            type: 'declarative',
            d: undefined
        },
        outer: <GlobalLexicalEnvironment>,
        this: <utils>
    },
}

После того, как контекст выполнения создан, он входит в стадию выполнения, и переменные присваиваются, считываются и переназначаются в процессе выполнения. пока программа не дойдет до конца. Мы замечаем, что при создании контекста выполнения переменнаяa``bобе<uninitialized>, покаsumинициализируется какundefined. Вот почему вы можете получить доступ до объявленияvarопределенная переменная (подъем переменной), при доступеlet/constОпределенная переменная сообщит о причине эталонной ошибки.

пусть/константа и вар

Кратко поговорим об одном и том же объявлении переменной, в чем разница между ними?

Разница между let и const здесь повторяться не будет.

Место хранения
Из предыдущего раздела мы знаем, чтоlet/constОбъявленная переменная относится кLexicalEnvironment,а такжеvarОбъявленная переменная принадлежитVariableEnvironment.

инициализация (лексическая фаза)
let/constбудет установлен на<uninitialized>бит флага, когда не выполняетсяlet xxxилиlet xxx = ???(строка назначения), предварительное чтение переменной сообщитReferenceErrorошибка. (Эту функцию также называют暂时性死区)varВо время инициализации он сначала назначается какundefined, даже если строка назначения не выполнена, ее все равно можно прочитатьvarПеременная(undefined).

запись среды блока (область блока)
существуетстандарт ECMAупоминается при встречеBlockилиCaseBlock, будет создана новая запись окружения, объявленная в блокеlet/constПеременные, функции, классы хранятся в этой новой записи среды, эти переменные и блокисильная привязка, эти объявленные переменные не могут быть прочитаны вне блока. Эта функция известна как блочная область.

Что такое Блок? Блоки заключены в фигурные скобки ({}).

существуетBlockсерединаlet/constПеременные допустимы только в блоке, и переменные в блоке не могут быть прочитаны вне блока.varНа переменные это ограничение не распространяется.

varНезависимо от того, где она находится, переменная будет продвигаться ~

Отличия от ES3

Если вы знаете, что в версии ES5 говорится о контекстах выполнения, вам интересно, почемуVO,AO, область действия, цепочка областей действия и т. д. в этой статье не упоминаются. На самом деле два понятия не конфликтуют, одно — это определение в спецификации ES3, а лексическое окружение — это определение в спецификации ES6. Другие времена, другие имена.

ES3 --> ES6 Область видимости --> Лексическое окружение Цепочка областей действия --> внешняя ссылка VO|AO --> Запись среды вотобсуждение на stackoverflow

Вы спрашиваете меня, что я должен изучать? Опираясь на настоящее, помните историю и принимайте будущее.

Суммировать

Эта статья содержит много теоретических знаний о контексте выполнения, и ее нелегко усвоить и понять сразу, поэтому рекомендуется постепенно переваривать ее и перечитывать несколько раз. Когда вы знакомы с контекстом выполнения и лексической средой, я полагаю, вам будет легче понять больше функций и концепций JS.

Ссылаться на

наконец

Кодовые слова непросты, если:

  • Эта статья полезна для вас, пожалуйста, не скупитесь на свои ручонки, чтобы понравиться мне;
  • Если есть что-то, что вы не понимаете или ошибаетесь, пожалуйста, прокомментируйте, и я активно отвечу или внесу опечатки;
  • Я с нетерпением жду продолжения изучения технических знаний вместе со мной, пожалуйста, следуйте за мной;
  • Пожалуйста, укажите источник;

Ваша поддержка и внимание - самая большая мотивация для меня продолжать творить!

Эта статья впервые появилась на моемРепозиторий блога