Интерпретируйте замыкание, на этот раз из лексического окружения ECMAScript, контекст выполнения

JavaScript
Интерпретируйте замыкание, на этот раз из лексического окружения ECMAScript, контекст выполнения

Для фронтенда с X лет опыта работы над проектом тоже было сделано немало, и каждая сцена тоже подвергалась некоторым воздействиям. Но если вы действительно хотите, чтобы интервьюер открылся, чтобы разорвать принцип, то все равно немного паникуйте. Видя, что многие великие боги рвут на части различные принципы фреймворка, я все же немного завидую их технической мощи.Зависть не так хороша, как действие. Хм... поговорим сегодня о закрытии!

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

好像懂了吗

Различные этапы делают разные вещи. Когда есть некоторые основы, мы все еще можем правильно изучить принципы, и не плавать на поверхности проблемы! Итак, что мы должны делать со средним уровнем технического уровня, и как пробиваться через эти грязные статьи? Я думаю, что один способ - найти подсказки от некоторых авторитетных документов, таких как спецификация ES, MDN, Wikipedia и т. Д.

оЗакрытие(закрытие), всегда с разной интерпретацией.

Во-первых, замыкания определяютсяфункцияи объявление функцииЛексическое окружениекомбинированный. Это утверждение исходит изMDN – Закрытие.

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

Из того, что я понимаю, я думаю, что первое утверждение верно, замыкание — это не функция, а функция и лексическое окружение. Так верно ли второе утверждение? Я думаю, что это наполовину правильно, в случае замыканий функция имеет доступ к переменным в области действия другой функции, но замыкание не является функцией.

Это конец? Очевидно нет! Интерпретируйте закрытие, на этот раз давайте докопаемся до сути (хвастовство)!

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

Возвращаясь к теме, упомянутой вышеЛексическое окружение(лексическое окружение) Что это такое?

Лексическое окружение

Мы можем посмотреть второй раздел в главе 10 (Исполняемый код и контекст выполнения) спецификации ES5.Лексическое окружениечто сказать.

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code.

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

Вопрос в том, что такое тип спецификации? Тип спецификации является типом Type. Из спецификации ES5 видно, что Type делится наlanguage typesа такжеspecification typesДве категории.

类型示意图

Типы языка — это типы языка, типы, с которыми мы знакомы, то есть типы данных, которыми могут манипулировать программисты, использующие ECMAScript, в том числеUndefined, Null, Number, String, Booleanа такжеObject.

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

A specification type corresponds to meta-values that are used within algorithms to describe the semantics of ECMAScript language constructs and ECMAScript language types.

Что касается мета-значения, я думаю, его можно понять какметаданные, и что означают метаданные, вы можете просто взглянуть на эти знанияЧто такое метаданные? Зачем нужны метаданные?

В основном,Метаданные — это данные, используемые для описания данных. Эту точку можно сравнить с языком высокого уровня, всегда использующим язык нижнего уровня и структуры данных для описания и выражения. Это то, что делает движок JS.

Получив общее представление о том, что такое спецификационный тип, мы не можем не задаться вопросом: что содержит спецификационный тип?

The specification types are Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record.

Увидев это, я, кажется, что-то понял.Лексическое окружение(лексическое окружение) иЭкологические записи(Environmental Record) является своего родаканонический тип(тип спецификации), это действительно концепция более низкого уровня.

отпуститьList, Completion, Property Descriptor, Property IdentifierНе будем говорить о типе спецификации, давайте посмотрим на тип спецификации Lexical Environment.

Следующее предложение объясняет, что на самом деле содержит лексическое окружение:

A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.

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

Лексическое окружение имеет следующую структуру:

Lexical Environment
  + Outer Reference
  + Environment Record

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

词法环境链表示意图

Usually a Lexical Environment is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a WithStatement, or a Catch clause of a TryStatement and a new Lexical Environment is created each time such code is evaluated.

Обычно лексическое окружение связано с некоторой конкретной синтаксической структурой кода ECMAScript (например,FunctionDeclaration,WithStatementилиTryStatementизCatchпункт), и каждый раз при оценке такого кода создается новая лексическая среда.

PS: оценка - это причастие прошедшего времени от оценки. Буквально это означает оценку, и я не думаю, что код оценки очень прост для понимания. Мое личное понимание состоит в том, что оценка кода представляет собойJS-движок интерпретирует и выполняет код javascript.

Мы знаем, что выполнение функции создает новую лексическую среду.

Мы также согласны с тем, что оператор with «расширяет» область действия (фактически вызывает NewObjectEnvironment, создавая новую лексическую среду, запись среды которой является записью среды объекта).

Это то, что мы понимаем лучше. Так что же делает предложение catch с лексическим окружением? Хотя обычно часто используется try-catch, многие люди не замечают деталей лексического окружения, в том числе и я!

Как мы знаем, в предложении catch будет объект ошибки.e

function test(value) {
  var a = value;
  try {
    console.log(b);
    // 直接引用一个不存在的变量,会报ReferenceError
  } catch(e) {
    console.log(e, arguments, this)
  }
}
test(1);

существуетcatchпечатать в пунктеarguments, просто для демонстрацииcatchПредложение не является функцией. потому что, еслиcatchэто функция, явно напечатанная здесьargumentsне должно бытьtestфункциональныйarguments. теперь, когдаcatchне является функцией, так почему же может бытьcatchобъект ошибки, к которому обращаются в предложенииe?

ответcatchПредложение использует NewDeclarativeEnvironment для создания нового лексического окружения (ссылка на внешнее лексическое окружение лексического окружения в предложении catch указывает на лексическое окружение функционального теста), а затем связывает идентификатор e с записью окружения нового лексического окружения. через CreateMutableBinding и SetMutableBinding .

Некоторые скажут,forв петлеinitializationчасть тоже может пройтиvarопределить переменные иcatchЕсть ли существенные различия между пунктами? Обратите внимание, что до ES6 не было блочной области видимости. существуетforцикл черезvarОпределенные переменные в принципе принадлежат лексическому окружению функции, в которой они расположены. еслиforоператор не используется в функции, то он передаетсяvarОпределенные переменные принадлежат глобальной среде (The Global Environment).

Новое лексическое окружение устанавливается в операторе with и предложении catch.Доказательство этого вывода исходит из приведенного выше предложения «новое лексическое окружение создается каждый раз, когда оценивается такой код».12.10 The with Statementа также12.14 The try Statement.

Environment Record

Поймите лексическое окружение (Lexical Environment), а затем расскажите о лексическом окруженииЭкологические записи(Отчет об окружающей среде). Записи среды тесно связаны с переменными и функциями, которые мы используем, можно сказать, что записи среды являются их базовой реализацией.

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

В этой спецификации используются два вида значений Environment Record: декларативные записи окружения и записи объектного окружения. // длинный абзац пропущен

Из спецификации видно, что существует два типа записей среды:

  • declarative environment recordsДекларативные записи среды
  • object environment recordsзапись среды объекта

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

Эти общедоступные абстрактные методы:

  • HasBinding(N)
  • CreateMutableBinding(N, D)
  • SetMutableBinding(N,V, S)
  • GetBindingValue(N,S)
  • DeleteBinding(N)
  • ImplicitThisValue()

В декларативных записях окружения также должны быть реализованы два уникальных метода:

  • CreateImmutableBinding(N)
  • InitializeImmutableBinding(N,V)

По поводу неизменяемой привязки (ImmutableBinding) в спецификации есть такое подробное описание сцены:

Если strict имеет значение true, то вызовите конкретный метод CreateImmutableBinding env, передав в качестве аргумента строку «arguments».

Вызовите конкретный метод InitializeImmutableBinding env, передав «аргументы» и argsObj в качестве аргументов.
В противном случае вызовите конкретный метод CreateMutableBinding среды env, передав «аргументы» String в качестве аргумента.
Вызовите конкретный метод SetMutableBinding env, передав «аргументы», argsObj и false в качестве аргументов.

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

Аргументы можно изменить в нестрогом режиме:

function test(a, b) {
  arguments = [3, 4];
  console.log(arguments, a, b)
}
test(1, 2)
// [3, 4] 1 2

В строгом режиме изменение точки аргументов напрямую сообщит об ошибке:

"use strict";
function test(a, b) {
  arguments = [3, 4];
  console.log(arguments, a, b)
}
test(1, 2)
// Uncaught SyntaxError: Unexpected eval or arguments in strict mode

Обратите внимание, что я говорю здесьИзменить точку аргументов, вместоИзменить аргументы.arguments[2] = 3Эта операция не сообщит об ошибке в строгом режиме.

Таким образом, неизменяемая привязка (ImmutableBinding) ограничивает неизменность ссылки, а не неизменность объекта, на который указывает ссылка.

declarative environment records

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

object environment records

Записи среды объекта (записи среды объекта), включая Program, WithStatement и записи среды глобальной среды, упомянутые ниже. Они связывают привязки идентификаторов со свойствами определенных объектов.

Увидев это, я хочу спросить себя:identifier bindingsЧто это такое?

Посмотрев на абстрактные методы Environment Record, упомянутые в спецификации ES5, у меня есть общий ответ.

Давайте кратко рассмотрим процесс значения и присвоения переменной javascript:

var a = 1;
console.log(a);

мы даем переменнуюaинициализировать и назначить1Такой шаг фактически отражается в движке JS, который выполняетсяCreateMutableBinding(создать изменяемую привязку) иSetMutableBinding(установите значение изменяемой привязки).

в то время как на переменнойaКогда значение принимается, оно отражается в движке JS и выполняется.GetBindingValue(Получить связанное значение), в этих процессах выполнения будут некоторые утверждения и суждения, а также будут задействованы суждения строгого режима.10.2.1.1 Declarative Environment Records.

Здесь также опущены некоторые шаги, напримерGetIdentifierReference, GetValue(V), PutValue(V)Ждать.

Насколько я понимаю, привязки идентификаторов — это набор поддерживаемых в движке JSобязательные отношения, который можно использовать с javascript видентификаторсвязанные.

The Global Environment

Глобальная среда — это специальная лексическая среда, которая создается перед выполнением кода ECMAScript. Запись среды в глобальной среде — это запись среды объекта, которая привязана кглобальный объект(Глобальный объект), отраженный всреда браузера, тот, который связан с глобальным объектом,оконный объект.

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

существует15.1 The Global ObjectВ этом разделе также объясняются некоторые детали Global Object, например, почемуnew Window(), почему глобальные объекты такие разные в разных средах хостинга...

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

После их прочтения у нас все еще нет полного понимания того, как интерпретироватьЗакрытие, Давайте взглянемконтекст выполнения. В моем предыдущем понимании контекст должен быть средой, содержащей переменные, доступные коду. Конечно, это явно недостаточно полно. Так что же такое контекст?

When control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this logical stack is the running execution context.

Когда управление программой передаетсяИсполняемый код ECMAScript(исполняемый код), он входит в контекст выполнения (контекст выполнения), контекст выполнения является логическимструктура стека(Куча). Самый верхний контекст выполнения в стеке — это текущий контекст выполнения.

многие люди правыисполняемый кодОпять могут возникнуть сомнения, а разве javascript не является исполняемым кодом? Не похожПримечания(Комментарий),пробел(Пробел) не является исполняемым кодом.

An execution context contains whatever state is necessary to track the execution progress of its associated code.

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

  • LexicalEnvironment: Лексическое окружение
  • VariableEnvironment: переменная среда
  • ThisBinding: ключевое слово this напрямую связано с контекстом выполнения

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

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

Когда элемент управления входит в контекст выполнения, устанавливается ThisBinding контекста выполнения, определяются его VariableEnvironment и начальная LexicalEnvironment, а также выполняется создание экземпляра привязки объявления (10.5).Точный способ выполнения этих действий зависит от типа вводимого кода.

Когда управляющая программа входит в контекст выполнения, происходят следующие три действия:

  1. Значение этого ключевого слова установлено.
  2. В то же время определяются VariableEnvironment (неизменная) и исходная LexicalEnvironment (может меняться, поэтому здесь исходная).
  3. Затем выполните операцию инициализации декларативной привязки.

Детали выполнения этих действий зависят от типа кода (делится наglobal code, eval code, function codeтри категории).

PS: Обычно VariableEnvironment и LexicalEnvironment согласованы во время инициализации, VariableEnvironment не изменится, а LexicalEnvironment может измениться во время выполнения кода.

Итак, что произойдет с контекстом выполнения при вводе глобального кода, кода eval и кода функции? Если вам интересно, пожалуйста, внимательно прочитайте10.4 Establishing an Execution Context.

Структура связанного списка лексического окружения

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

词法环境链表示意图

Как мы все знаем, при понимании замыканий многие упоминаютцепочка прицелов(Scope Chain) такая концепция также приведет кVO(переменный объект) иAO(активный объект) эти понятия. Однако когда я прочитал спецификацию ECMAScript, я не нашел этих ключевых слов повсюду. Я подумал, является ли структура связанного списка лексической среды тем, что они называют цепочкой области видимости? ВО, АО - устаревшее понятие? Но эти понятия как бы стали "авторитетными".Когда ищу статьи по теме,все говорят ВО,АО.Неужели я хочу в этом разбираться?

В ECMAScript найдите8.6.2 Object Internal Properties and Methodsв разделеTable 9 Internal Properties Only Defined for Some Objects, такое внутреннее свойство [[Scope]] действительно есть.По смыслу слова Scope [[Scope]] не является областью видимости функции!

В этой таблице мы можем ясно видеть, что значение столбца Value Type Domain в [[Scope]] равноLexical Environment, что означает, что [[Scope]] являетсяЛексическое окружение. Давайте посмотрим на Описание:

A lexical environment that defines the environment in which a Function object is executed. Of the standard built-in ECMAScript objects, only Function objects implement [[Scope]].

Посмотрите внимательно, [[Scope]]Среда, в которой выполняется объект функции, и только функции реализуют свойство [[Scope]], что означает, что [[Scope]] является свойством, зависящим от функции.

Итак, могу ли я понять это как: цепочка областей видимости (Scope Chain) — этоЦепочка лексических окружений, к которым можно получить доступ при выполнении функции.而广义上的词法环境链表不仅包含了作用域链,还包括WithStatement和Catch子句中的词法环境,甚至包含ES6的Block-Level词法环境。这么看来,ECMAScript是非常严谨的!

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

  • VO является продуктом этапа лексического разбора.
  • AO является продуктом фазы исполнения кода (Execution).

В спецификациях ES5 и ES6 такого слова нет, так что просто забудьте про VO, AO!

Закрытие

Что такое закрытие?

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

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

Это объяснение того, что такое замыкания с точки зрения информатики, и, конечно же, то же самое относится и к javascript!

В нем есть слово "свободная переменная", это переменная, на которую мы обращаем внимание в лексическом окружении замыкания.

Как Chrome определяет закрытие?

Браузер Chrome, кажется, стал стандартом для внешнего интерфейса, так как же в браузере Chrome определяется закрытие? Приходите и исследуйте!

function test() {
  var a = 1;
  function increase() {
    debugger;
    var b = 2;
    a++;
    return a;
  };
  increase();
}
test();

闭包1

Я помещаю отладчик внутри функцииincrease, при отладке мы прямо смотрим на выделенную часть справа, и можем обнаружить, что в Scope есть Closure, а имя Closure — это внешняя функцияtestИмя функции, переменная в замыканииaнаходится в функцииtestопределено в , а переменнаяbявляется локальной переменной вLocalсередина.

PS: локальные переменные см.localEnv.

Предположим, я нахожусь во внешней функцииtestопределить другую переменную вc, а во внутренней функцииincreaseЧто произойдет, если мы не процитируем его?

function test() {
  var a = 1;
  var c = 3; // c不在闭包中
  function increase() {
    debugger;
    var b = 2;
    a++;
    return a;
  };
  increase();
}
test();

Проверено, внутренняя функцияincreaseПри выполнении переменнаяcНе в замыкании.

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

Итак, здесь мы можем сделать следующий вывод,Необходимые условия для генерации замыканияДа:

  1. Есть вложенность функций;
  2. Вложенные внутренние функции должны ссылаться на переменные, определенные во внешних функциях;
  3. Вложенные внутренние функции должны выполняться.

Замыкания, о которых интервьюеры любят спрашивать

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

function test() {
  var a = 1;
  function increase() {
    a++;
  };
  function getValue() {
    return a;
  }
  return {
    increase,
    getValue
  }
}
var adder = test();
adder.increase(); // 自增1
adder.getValue(); // 2
adder.increase();
adder.getValue(); // 3

В этом примере мы обнаруживаем, что каждый раз, когда мы вызываемadder.increase()После метода,aзначение будет увеличиваться по сравнению с прошлым разом1, то есть переменнаяaудерживается в памяти без освобождения.

Так что в конце концов, как этот вопрос за этим явлением?

Анализ закрытия

Поскольку закрытие связано с проблемами памяти, я должен упомянуть механизм GC (сборки мусора) V8.

Большинство известных нам из книг стратегий сборщика мусора — это подсчет ссылок, но современные основные виртуальные машины (включая V8, JVM и т. д.) не используют стратегии сбора подсчета ссылок, а используют алгоритмы достижимости.

Подсчет ссылок относительно прост для понимания, поэтому он часто встречается в учебниках, но может возникнуть проблема, связанная с тем, что объекты ссылаются друг на друга и их память не может быть освобождена. Алгоритм достижимости начинается с объекта GC Roots (такого как окно глобального объекта) для поиска уцелевших (достижимых) объектов, недоступные объекты будут переработаны, а уцелевшие объекты подвергнутся серии обработки.

Относительно некоторых деталей алгоритма V8 GC есть статья, в которой очень хорошо рассказывается, автор Shaying, настоятельно рекомендуется ознакомиться.

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

function test() {
  var a = 1;
  function increase() {
    a++;
  };
  function getValue() {
    return a;
  }
  return {
    increase,
    getValue
  }
}
var adder = test();
adder.increase();
adder.getValue();
  1. Первоначально выполните глобальный код, создайте глобальный контекст выполнения и установите его соответствующим образом.thisЗначение ключевого словаwindowОбъект для создания глобальной среды. под глобальным объектомadder, testи т. д. объявления переменных и функций.

  1. Начать выполнениеtestфункция, введитеtestКонтекст выполнения функции. существуетtestВо время выполнения функции объявляются переменныеa, функцияincreaseа такжеgetValue. Наконец возвращает объект, два свойства которого относятся к функцииincreaseа такжеgetValue.

  1. покидатьtestконтекст выполнения функции,testРезультат выполнения функции присваивается переменнойadder, текущий контекст выполнения восстанавливается до глобального контекста выполнения.

  1. передачаadderизincreaseметод, введитеincreaseКонтекст выполнения функции, код выполнения делает переменнуюaавтоматическое приращение1.

  1. покидатьincreaseКонтекст выполнения функции.
  2. передачаadderизgetValueметод, его процедура и вызовincreaseПроцесс метода аналогичен.

После определенного понимания всего процесса выполнения кажется сложным объяснить, почему переменные в замыканииaНе будет перерабатываться GC. Ясно только одно, а именно то, что каждая казньincreaseа такжеgetValueметод зависит от функцииtestпеременные, определенные вa, но сам по себе этот факт явно неубедителен.

Вот вопрос, как парсится кодaЧто с этим идентификатором?

Прочитав спецификацию, мы можем узнать, что синтаксический анализ идентификаторов выполняется черезGetIdentifierReference(lex, name, strict)lexэто лексическая среда,nameимя идентификатора,strictлогический флаг для строгого режима.

затем выполнение функцииincreaseКогда, как разрешать идентификаторaчто о? Давайте проанализируем это!

  1. Во-первых, пустьlexзначение функцииincreaseизlocalEnv(локаль функции), черезGetIdentifierReference(lex, name, strict)существуетlocalEnvразрешать идентификаторы вa.
  2. согласно сGetIdentifierReferenceИсполнение логикиlocalEnvне разрешается в идентификаторa(потому чтоaне работаетincreaseобъявлено в , что очевидно), поэтому пойдет наlocalEnvВнешняя лексическая среда продолжает искать, и эта внешняя лексическая среда фактическиincreaseВнутреннее свойство функции [[Scope]] (к этому я пришел, много раз читая определение спецификации), то естьtestфункциональныйlocalEnvиз"кастрированная версия".
  3. вернуться к выполнению функцииtestНа этом шаге выполняется функцияtestПосле этого функцияtestсерединаlocalEnvПривязки других переменных в могут быть освобождены в последующем процессе GC, за исключениемaПривязка не может быть освобождена, так как существуют другие лексические окружения (increaseвнутреннее свойство функции [[Scope]]) будет ссылаться наa.
  4. Лексические окружения и функции замыканийtestво время выполненияlocalEnvразные. функцияtestПри выполнении егоlocalEnvполностью переинициализируется и выйдет из функцииtestПосле контекста выполнения замыкающей лексической среды замыкающая лексическая среда сохраняет только часть привязок в своей записи среды, и на эту часть привязок будут ссылаться другие лексические среды, поэтому я называю ее «кастрированной версией».

Некоторые друзья могут задать вопрос здесь (я тоже задавал себе такой же), почемуadder.increase()вызывается в глобальном контексте выполнения, внешнее лексическое окружение при его выполнении по-прежнемуtestфункциональныйlocalEnv"Кастрированная версия"?

Это возвращается к определению ссылки на внешнюю лексику, которая относится кЛексическое окружение, которое логически окружает внутреннее лексическое окружение.!

The outer reference of a (inner) Lexical Environment is a reference to the Lexical Environment that logically surrounds the inner Lexical Environment.

Плюсы и минусы закрытия

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

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

Замыкания — это палка о двух концах, к тому же у них есть такой очевидный недостаток:

  • Существует вероятность того, что переменная находится в памяти, и память, которую она занимает, не может быть освобождена сборщиком мусора, что приводит к переполнению памяти.

резюме

Эта статья начинается со спецификации ECMAScript и шаг за шагом демистифицирует замыкание. Во-первых, мы понимаем лексическое окружение из определения замыкания, а из лексического окружения выводятся такие понятия, как записи окружения, ссылки на внешнее лексическое окружение и контексты выполнения. После сомнений по поводу старых концепций, таких как VO, AO и т. д., я решил поискать подсказки в спецификации и, наконец, получил подсказку. При интерпретации замыканий я искал различные источники, начиная с общего определения замыканий в информатике, сопоставляя некоторые ключевые понятия с javascript и комбинируя некоторые точки знаний GC, у меня есть ответ.

На написание этой статьи ушло много времени, потому что, когда речь идет о спецификации ECMAScript, некоторые описания должны быть объективными и строгими. Должны быть субъективные элементы в процессе интерпретации, если есть ошибки, пожалуйста, укажите!

Наконец, настоятельно рекомендуется прочитать спецификацию ECMAScript, когда у вас будет время. Чтение спецификации языка — отличный способ демистифицировать и лучше понять основы языка. Например, если мы не знаем логику выполнения оператора, безопаснее всего посмотреть спецификацию языка напрямую!

В конце прикреплена картинка, которая поможет вам понять спецификацию ECMAScript.

Если удобно, поставьте лайк, спасибо! добро пожаловать, чтобы добавить меня wechatlaobaifeТакже доступны общение, технические встречи с друзьями и светская беседа.

использованная литература