Интервьюер: Расскажите мне о контексте казни.

JavaScript
Интервьюер: Расскажите мне о контексте казни.

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

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

когдаJSКогда движок анализирует исполняемый фрагмент кода (обычно фаза вызова функции), он сначала выполняет некоторые подготовительные действия перед выполнением."Готов к работе", называется"контекст выполненияEC)"или также называетсясреда выполнения.

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

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

javascriptСуществует три типа контекста выполнения, а именно:

  • глобальный контекст выполнения- Это контекст выполнения по умолчанию или самый простой.В программе есть только один глобальный контекст, который используется во всей программе.javascriptВ течение жизненного цикла сценария он будет находиться в нижней части стека выполнения и не будет уничтожен при извлечении стека. Глобальный контекст сгенерирует глобальный объект (в качестве примера возьмем среду браузера, этот глобальный объектwindow), и воляthisЗначение привязано к этому глобальному объекту.

  • контекст выполнения функции- При каждом вызове функции создается новый контекст выполнения функции (независимо от того, вызывается ли функция повторно)

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

Содержимое контекста выполнения ES3

Контекст выполнения — это абстрактное понятие, мы можем понимать его какobject, контекст выполнения включает следующее:

  1. переменный объект
  2. Активный объект
  3. цепочка прицелов
  4. информация о вызывающем абоненте

переменный объект (variable objectкороткое имяVO)

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

Каждый контекст выполнения имеет объект, представляющий переменную -переменный объект, переменный объект глобальной среды выполнения всегда существует, а переменная локальной среды, такая как функция, будет существовать только во время выполнения функции.Когда функция вызывается и до запуска кода конкретной функции, механизм JS будет использовать текущую функциюсписок параметров(arguments) инициализирует «переменный объект» и связывает с ним текущий контекст выполнения, объявленный в блоке функционального кодаПеременнаяифункциябудет добавлен как свойство к этому объекту переменной.

变量对象的创建细节

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

// 这种叫做函数声明,会被加入变量对象
function a () {}

// b 是变量声明,也会被加入变量对象,但是作为一个函数表达式 _b 不会被加入变量对象
var b = function _b () {}

Объекты-переменные в глобальном контексте выполнения и в контексте выполнения функции также немного отличаются, разница между ними просто:

  1. Переменный объект в глобальном контексте является глобальным объектом, в среде браузера этоwindowобъект.
  2. Свойства, определенные внутри объекта переменной в контексте выполнения функции, нельзя получить доступ напрямую, только при вызове функции переменный объект (VO) активируется как активный объект (AO), мы можем получить доступ к его свойствам и методам.

Активный объект (activation objectкороткое имяAO)

Оригинал: когда элемент управления входит в контекст выполнения кода функции, создается объект, называемый объектом активации, который связывается с контекстом выполнения. Объект активации инициализируется свойством с именем, аргументами и атрибутами { DontDelete }. Начальное значение этого свойства является объектом аргументов, описанным ниже.Объект активации затем используется в качестве объекта переменной для целей создания экземпляра переменной.

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

По сути, переменный объект и активный объект — это одно, но в разных состояниях и стадиях.

цепочка прицелов (scope chain)

сфераУказывает, как найти переменные, то есть определить права доступа выполняемого в данный момент кода к переменным. При поиске переменной сначала будет выполняться поиск в объекте-переменной текущего контекста, если не будет найдено, то будет поиск в объекте-переменной контекста исполнения родителя (родителя на лексическом уровне), и всегда найти переменный объект глобального контекста, то есть глобальный объект. Таким образом, связанный список, состоящий из переменных объектов нескольких контекстов выполнения, называетсяцепочка прицелов.

Объем функции определяется при ее создании. Когда функция будет создана, появится[[scope]]Внутреннее свойство сохраняет в нем все объекты родительских переменных. Когда функция выполняется, создается среда выполнения, а затем[[scope]]Объект в свойстве строит цепочку областей действия среды выполнения, а затем переменный объектVOактивирован для генерацииAOИ добавьте его в начало цепочки областей действия, будет создана полная цепочка областей действия:

Scope = [AO].concat([[Scope]]);

Вызывающий код текущего исполняемого блока кода (этот)

Если текущая функция вызывается как метод объекта или используетсяbind call applyЖдатьAPIВыполните вызов делегата, информация о вызывающем абоненте текущего блока кода (this value) сохраняется в текущем контексте выполнения, в противном случае по умолчанию используется глобальный вызов объекта.

оthisДетали создания немного раздражают, вы можете войти, если вам интереснопорталучиться.

Моделирование структуры данных контекста выполнения

Если приведенный выше полный контекст выполнения выражен в виде кода, он должен выглядеть следующим образом:

executionContext:{
    [variable object | activation object]:{
        arguments,
        variables: [...],
        funcions: [...]
    },
    scope chain: variable object + all parents scopes
    thisValue: context object
}

Жизненный цикл контекста выполнения ES3

Жизненный цикл контекста выполнения состоит из трех фаз, а именно:

  • создать сцену
  • этап выполнения
  • фаза разрушения

создать сцену

Этап создания контекста выполнения функции происходит при вызове функции и до выполнения определенного кода в теле функции.Во время этапа создания JS-движок сделает следующее:

  • с текущей функциейсписок параметров(arguments) инициализирует «переменный объект» и связывает с ним текущий контекст выполнения, объявленный в блоке функционального кодаПеременнаяифункциябудет добавлен как свойство к этому объекту переменной.На этом этапе инициализируются и объявляются переменные и функции, а переменные единообразно определяются какundefinedВам нужно дождаться, пока значение будет присвоено, и функция будет определена напрямую.

    Вам показалось это описание, выделенное жирным шрифтом, очень знакомым? Да, эта операцияПодъем объявления переменной(объявления переменных и функций поднимаются, но сначала поднимаются функции).

  • Создайте цепочку областей действия (детали построения были описаны ранее)

  • Конечноthisзначение

этап выполнения

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

фаза разрушения

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

Обратите внимание, что это только общий случай, случай замыканий отличается.

Определение закрытия:Функция, которая имеет доступ к переменной внутри другой функции. Проще говоря, если функция используется как возвращаемое значение другой функции и на нее ссылаются извне, то функция называется замыканием.

function funcFactory () {
    var a = 1;
    return function () {
        alert(a);
    }
}

// 闭包
var sayA = funcFactory();
sayA();

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

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

заES3Мы можем использовать следующий список, чтобы обобщить весь процесс выполнения программы:

  1. функция называется
  2. Перед выполнением кода конкретной функции создается контекст выполнения.
  3. Войдите в фазу создания контекста выполнения:
    1. Инициализировать цепочку областей видимости
    2. Создайтеarguments objectПроверяйте параметры в контексте, инициализируйте имена и значения и создавайте эталонные копии
    3. Просканируйте контекст, чтобы найти все объявления функций:
      1. Для каждой найденной функции создайте свойство в объекте переменной с их собственным именем функции, которое содержит указатель на фактический адрес памяти.
      2. Если имя функции уже существует, указатель ссылки на свойство будет перезаписан.
    4. сканировать контекст, чтобы найти всеvarобъявление переменной:
      1. Для каждого найденного объявления переменной создайте свойство в объекте переменной с собственным именем переменной и используйтеundefinedинициализировать
      2. Если имя переменной уже существует как свойство в объекте переменной, ничего не делайте и сканируйте следующее
    5. Конечноthisценность
  4. Войдите в фазу выполнения контекста выполнения:
    1. Запускайте/интерпретируйте код функции в контексте и присваивайте значения переменных по мере выполнения кода строка за строкой.

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

ES5норма правоES3Часть концепции контекста выполнения была скорректирована.Самая важная корректировка заключается в удаленииES3средние переменные объекты и активные объекты дляКомпоненты лексического окружения ( LexicalEnvironment component)икомпонент переменной среды ( VariableEnvironment component)альтернатива. такES5Контекст выполнения концептуально представлен следующим образом:

ExecutionContext = {
  ThisBinding = <this value>,
  LexicalEnvironment = { ... },
  VariableEnvironment = { ... },
}

Лексические окружения в ES5

ES6 официальныйОпределение лексического окружения в:

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

Проще говоряЛексическое окружениеявляется холдингомотображение идентификатор-переменнаяСтруктура. здесьидентификаторотносится к имени переменной/функции, аПеременнаяЯвляется ссылкой на фактический объект (включая объекты функционального типа) или необработанные данные.

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

Переменная среда в ES5

переменная средаэто такжеЛексическое окружение, поэтому обладает всеми свойствами лексического окружения.

Почему тамES5Нормативная сила концепции разделения изменчивой среды заключается вES6Подача: вES6середина,Лексическое окружениекомпоненты ипеременная средаОдно отличие состоит в том, что первый используется для хранения объявлений функций и переменных (letиconst) привязка, в то время как последняя используется только для храненияvarпривязка переменных.

На этапе создания контекста движок проверяет код на наличие объявлений переменных и функций, и переменные изначально устанавливаются как неопределенные (в случае var) или неинициализированные (в случаях let и const). Вот почему вы можете получить доступ к переменным, определенным с помощью var, перед объявлением (хотя и не определенным), но доступ к переменным, определенным с помощью let и const, до объявления приведет к ошибке ссылки.

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

заES5Мы можем использовать следующий список, чтобы обобщить весь процесс выполнения программы:

  1. Программа запускается и создается глобальный контекст
    1. создать глобальный контекстЛексическое окружение
      1. СоздайтеРекордер объектной среды, который используется для определения вхождений вглобальный контекстСвязь между переменными и функциями в (отвечает за обработкуletиconstопределенная переменная)
      2. Создайтессылка на внешнюю среду, значениеnull
    2. создать глобальный контекстпеременная среда
      1. СоздайтеРекордер объектной среды, который держитоператор объявления переменнойПривязки, созданные в контексте выполнения (отвечают за обработкуvarПеременная определена, начальное значение равноundefinedпривести к снятию декларации)
      2. Создайтессылка на внешнюю среду, значениеnull
    3. КонечноthisЗначение является глобальным объектом (в качестве примера возьмем браузер, этоwindow)
  2. Вызывается функция, создается контекст функции
    1. создать контекст функцииЛексическое окружение
      1. СоздайтеДекларативный регистратор среды, в котором хранятся переменные, функции и параметры, он содержитargumentsобъект (в этом объекте хранится карта индексов и параметров) и параметры, переданные в функциюlength. (отвечает за обработкуletиconstопределенная переменная)
      2. Создайтессылка на внешнюю среду, значением является глобальный объект или родительская лексическая среда (область)
    2. создать контекст функциипеременная среда
      1. СоздайтеДекларативный регистратор среды, в котором хранятся переменные, функции и параметры, он содержитargumentsобъект (в этом объекте хранится карта индексов и параметров) и параметры, переданные в функциюlength. (отвечает за обработкуvarПеременная определена, начальное значение равноundefinedпривести к снятию декларации)
      2. Создайтессылка на внешнюю среду, значением является глобальный объект или родительская лексическая среда (область)
    3. Конечноthisценность
  3. Войдите в фазу выполнения контекста выполнения функции:
    1. Запускайте/интерпретируйте код функции в контексте и присваивайте значения переменных по мере выполнения кода строка за строкой.

Что касается смены контекста выполнения в ES5, лично мне кажется, что изменилась концепция, и суть не сильно отличается от ES3. Что касается цели изменения, то оно должно быть для let и const в ES6.

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

Когда скрипт запускается, он может вызывать множество функций и генерировать множество контекстов выполнения функций, поэтому возникает вопрос, как управлять этими контекстами выполнения? Для решения этой проблемы,javascriptМеханизм создает «стек контекста выполнения» (Execution context stackкороткое имяECS) для управления контекстом выполнения.

Как следует из названия, стек контекста выполнения имеет стековую структуру, поэтому следуетLIFO(Последний пришел, первый вышел), все контексты выполнения, созданные во время выполнения кода, будут переданы в стек контекстов выполнения для управления.

Когда движок JS начинает парсить код скрипта, он сначала создаетглобальный контекст выполнения, помещенный в нижнюю часть стека (этот глобальный контекст выполнения будет существовать в нижней части стека с момента создания до уничтожения программы).

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

рекурсия и переполнение стека

Разобравшись с механизмом работы стека вызовов, мы можем рассмотреть вопрос, можно ли бесконечно толкать этот стек контекста выполнения? Очевидно, что нет, сам стек выполнения также имеет ограничение емкости.Когда объекты контекста выполнения внутри стека выполнения в определенной степени отстают, если отставание продолжается, он сообщит о «переполнении стека (stack overflow)". Ошибки переполнения стека часто возникают врекурсиясередина.

栈溢出错误

Метод программирования, при котором программа вызывает саму себя, называется рекурсией.

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

// 求 1~num 的累加,此时 num 由外部传入,是未知的
function recursion (num) {
    if (num === 0) return num;
    return recursion(num - 1) + num;
}

recursion(100) // => 5050
recursion(1000) // => 500500
recursion(10000) // => 50005000
recursion(100000) // => Uncaught RangeError: Maximum call stack size exceeded

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

оптимизация хвостовой рекурсии

Для проблемы рекурсии «взрывающегося стека» мы можем узнатьоптимизация хвостовой рекурсии. «Рекурсия» мы видели, так что же означает «хвост»? «хвост» означает «хвостовой вызов (Tail Call)",которыйПоследним шагом функции является возврат результата выполнения функции:

// 尾调用正确示范1
function a(x){
  return b(x);
}

// 尾调用正确示范2
// 尾调用不一定要写在函数的最后为止,只要保证执行时是最后一部操作就行了。
function c(x) {
  if (x > 0) {
    return d(x);
  }
  return e(x);
}

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

Мы можем переписать приведенную выше кумулятивную рекурсию, используя хвостовые вызовы:

// 尾递归优化
function recursion (num, sum = 0) {
    if (num === 0) return sum;
    return recursion(num - 1, sum + num);
}

recursion(100000) // => Uncaught RangeError: Maximum call stack size exceeded

duang

Почему он все еще сообщает об ошибке после запуска 😳? ? Он треснул. . .

На самом деле оптимизация хвостовой рекурсии — это такая штука,Ни один из браузеров в настоящее время не поддерживается(предположительно Safari 13 поддерживается),babelКомпиляция также не поддерживается. ТотnodejsвнутреннийV8Что насчет двигателя? Это сделано, но это не работает для вас.Официальный ответ таков:

Proper tail calls have been implemented but not yet shipped given that a change to the feature is currently under discussion at TC39.

Причина, это также имеет смысл:

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

С менталитетом неудовлетворенности я началgoogleПосмотрите, как js может поддерживать хвостовую рекурсию.

Смотретьstackoverflowозначает, что толькоSafariПоддержка оптимизации хвостовой рекурсии, кажется, что есть игра, получите ее первойsafariПроверьте это.

Хорошо, что такое наследственный интерфейс? ? ? Забудь, запусти сначала.

мы согласилисьSafariМожет ли это быть? ? Продолжая искать причину, я обнаружил следующую картину, похожую наcaniuse:

Похоже толькоSafari 13+Да, версия на моем компьютере5.1Да, укуси пулю и найди13+версия. Коснитесь до упора, коснитесь официального сайта Apple:

нетwin10скачать версию? ? Я не могу купить еще один mbp, не так ли? (Нажмите на Alipay, чтобы посмотреть, забудьте об этом) ДаmbpБольшие парни могут попробовать и посмотреть, сработает ли это.iOS12+тоже может поддержать.

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

Мелкий измельчитель

Я нашел в Интернете несколько типичных вопросов для собеседования в контексте казни.Вы можете попробовать:

Первый вопрос:

var foo = function () {
    console.log('foo1');
}

foo();

var foo = function () {
    console.log('foo2');
}

foo();

Первый вопрос ничего, это надо уметь писать.

Второй вопрос:

foo();

var foo = function foo() {
    console.log('foo1');
}

function foo() {
    console.log('foo2');
}

foo();

Глобальная среда выполнения создается автоматически, и в процессе создаются объекты переменных для сбора атрибутов функциональных переменных, что приводит к продвижению объявлений функций и объявлений переменных. Поскольку объявление функции поднимается раньше, и еслиvarПри определении переменной, если обнаруживается, что существует определение функции с таким же именем, определение переменной пропускается.Вышеприведенный код фактически может быть записан следующим образом:

function foo () {
    console.log('foo2');
}

foo();

foo = function foo() {
    console.log('foo1');
};

foo();

Третий вопрос:

var foo = 1;
function bar () {
    console.log(foo);
    var foo = 10;
    console.log(foo);
}

bar();

barФункция запускается, объявление внутренней переменной продвигается, и когда в блоке исполняемого кода есть переменная доступа,Сначала найдите локальную область, нашел этоfooзаundefined,Распечатай. потомfooназначается как10,распечатать10.

Четвертый вопрос:

var foo = 1;
function bar () {
    console.log(foo);
    foo = 2;
}
bar();
console.log(foo);

Этот вопрос также является поиском по цепочке расследования,barработает вfooОн не определен локально, поэтому должен быть переменной верхней области видимости.

Пятый вопрос:

var foo = 1;
function bar (foo) {
    console.log(foo);
    foo = 234;
}
bar(123);
console.log(foo);

бегатьbarфункция будет123Число передается в качестве аргумента, поэтому операция по-прежнему находится в локальной области видимости.foo.

Вопрос 6:

var a = 1;

function foo () {
    var a = 2;
    return function () {
        console.log(a);
    }
}

var bar = foo();
bar();

В этом разделе в основном рассматриваются концепции замыканий и области действия функции, нам просто нужно помнить:Область верхнего уровня, к которой может получить доступ функция, определяется при объявлении функции, а там, где объявляется функция, находится область верхнего уровня, и она не имеет ничего общего с тем, где она выполняется.. В этом вопросе анонимная функция возвращается как замыкание и вызывается снаружи, но цепочка областей видимости внутри нее ссылается на переменный объект в родительской функции.a, поэтому при поиске в цепочке областей действия печатается2.

Вопрос 7:

"use strict";
var a = 1;

function foo () {
    var a = 2;
    return function () {
        console.log(this.a);
    }
}

var bar = foo().bind(this);
bar();

Этот вопрос исследует среду выполненияthisуказывает на проблему, так как доступ явно указан внутри замыканияthisсерединаaсвойства, а замыканиеbindСвязывание выполняется в глобальной среде, поэтому то, что распечатывается, находится в глобальном объекте.a.

Что касается последнего вопроса, друг в области комментариев сказал, что добавление и не добавление привязки — это одно и то же, поэтому я переключился на строгий режим. Следует отметить, что this в функции запрещено указывать на глобальную переменную в строгом режиме.

Суммировать

  • Когда функция запускается, она создает так называемый «контекст выполнения», который также можно назвать средой выполнения, который используется для сохранения некоторой информации, необходимой для запуска функции.
  • Все контексты выполнения будут управляться "стеком контекста выполнения" системы, который представляет собой данные структуры стека. Глобальный контекст всегда находится в нижней части стека. Всякий раз, когда функция выполняется для создания нового контекста, объект контекста будет Будет помещен в стек, но стек контекста имеет ограничение емкости, и если емкость превышена, стек переполнится.
  • Контекст выполнения внутренне хранит следующее:переменный объект,цепочка прицелов,это указывает наНеобходимые данные для запуска этих функций.
  • Подъем объявления переменных и функций запускается во время построения объекта переменных.
  • Когда код внутри функции выполняется, он сначала обращается к объекту локальной переменной, чтобы попытаться получить переменную. Если он не может ее найти, он поднимается по цепочке области видимости, чтобы найти ее. Если целевая переменная найдена, она вернется. Если не найдет, вернется.undefined.
  • Верхняя область, к которой может получить доступ функция, определяется, когда функция создается и сохраняется в области действия функции.[[scope]]В атрибуте это не имеет ничего общего с тем, где должна выполняться функция.
  • когда вызывается функцияthisуказывает на, в зависимости от вызывающего объекта обычно существует несколько способов изменить значение функции.thisЗначение: вызов объекта,call,bind,apply.

Эта статья была включена вСтолбец «Руководство по фронтенд-интервью»

Связанные ссылки

Рекомендуемый прошлый контент

  1. Тщательно разбирайтесь в троттлинге и анти-тряске
  2. [Основные] Принципы и применение протоколов HTTP и TCP/IP
  3. [Combat] webpack4 + ejs + express перенесет вас в многостраничную архитектуру проекта приложения
  4. Цикл событий в браузере
  5. Интервьюер: Расскажите о цепочке прототипов и наследовании.
  6. Интервьюер: Расскажите о масштабе и закрытиях.
  7. Интервьюер: Расскажите о модульности в JS.
  8. Интервьюер: Давайте поговорим о let и const.