Собеседование перед интерфейсом должно быть | Одна статья, чтобы понять замыкания в JavaScript

JavaScript

Эта статья переведена сblog.bit SRC.IO/Ах-новички…, автор Сухджиндер Арора, частично изменено содержание и изменено название.

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

Правильное понимание замыканий поможет вам писать более качественный, эффективный и чистый код. В свою очередь, это поможет вам стать лучшим разработчиком JavaScript.

Итак, в этой статье я попытаюсь объяснить, как работают замыкания и как они на самом деле работают в JavaScript.

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

На следующем рисунке показано замыкание:

闭包

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

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

Что такое лексический объем?

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

let a = 'global';
function outer() {
  let b = 'outer';
  function inner() {
    let c = 'inner'
    console.log(c);   // prints 'inner'
    console.log(b);   // prints 'outer'
    console.log(a);   // prints 'global'
  }
  console.log(a);     // prints 'global'
  console.log(b);     // prints 'outer'
  inner();
}
outer();
console.log(a);         // prints 'global'

это здесь,innerФункция может получить доступ в своей области видимости,outerОбласть действия функции и переменные, определенные в глобальной области видимости. а такжеouterФункция может обращаться к переменным, определенным в своей собственной области видимости и в глобальной области видимости.

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

Global {
  outer {
    inner
  }
}

Уведомление,innerфункцияouterокружен лексической областью действия функции, иouterЛексическая область видимости функции, в свою очередь, окружена глобальной областью видимости. поэтомуinnerфункция доступна вouterПричина для переменных, определенных в функциях и глобальной области видимости.

Демонстрация замыканий

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

Пример 1

function person() {
  let name = 'Peter';
  
  return function displayName() {
    console.log(name);
  };
}
let peter = person();
peter(); // prints 'Peter'

В этом коде мы вызываемpersonфункция, которая возвращает внутреннюю функциюdisplayNameи сохраните эту внутреннюю функцию вpeterв переменной. когда мы звонимpeterфункция (фактически ссылаетсяdisplayNameфункция), названиеPeterвыводится на консоль.

но мы неdisplayNameобъявлено в функцииnameпеременные, поэтому даже после возврата внешней функции функция может каким-то образом получить доступ к переменным своей внешней функции.person. следовательно,displayNameФункция фактически производит замыкание.

Пример 2

function getCounter() {
  let counter = 0;
  return function() {
    return counter++;
  }
}
let count = getCounter();
console.log(count());  // 0
console.log(count());  // 1
console.log(count());  // 2

Точно так же мы будемgetCounterАнонимная внутренняя функция, возвращаемая функцией, присваиваетсяcountПеременная.countТеперь функция является замыканием, даже еслиgetCounter()После выполнения к нему все еще можно получить доступgetCounterвнутри функцииcounterПеременная.

Но учтите, что каждый разcountпри исполненииcounterне сбрасывается на0.

Это потому, что каждый раз, когда вы звонитеcount(), создаст новую область действия для функции, но здесь толькоgetCounterСоздайте объем, и потому чтоcounterпеременная определена вgetCounterвнутри области действия функции, поэтому она будет вызываться каждый разcountУвеличивается вместо сброса при вызове.

Как работают замыкания

До сих пор мы обсуждали, что такое замыкания, и их практические примеры. Теперь давайте поймем, что замыкания находятся вJavaScriptкак это работает на самом деле.

Чтобы действительно понять, как работают замыкания в JavaScript, мы должны понимать JavaScript в двух наиболее важных концепциях, а именно: 1)контекст выполненияи 2)Лексическое окружение.

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

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

Выполняется только один контекст выполнения, поскольку JavaScript — это однопоточный язык, который управляется стеком выполнения или стеком вызовов.

Стек выполнения - это стек с конструкцией LIFO (последний в первом месте), а элементы могут быть добавлены или удалены только с верхней части стека.

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

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

执行上下文示例

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

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

执行栈

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

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

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

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

Лексическое окружение состоит из трех частей: (1) записи окружения; (2)outerНаправление внешней среды; (3)this

  1. Записи среды — это место, где хранятся объявления переменных и функций;
  2. outerТочка представления внешней среды, которую она может получить доступ к лексической среде внешнего слоя (родительского уровня). Это ключ к пониманию принципа закрытия.

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

lexicalEnvironment = {
  EnvironmentRecord: {
    <identifier> : <value>,
    <identifier> : <value>
  }
  outer: <Reference to the parent lexical environment>
  ThisBinding: <Global Object>
}

Теперь проанализируйте код ниже.

let a = 'Hello World!';
function first() {
  let b = 25;  
  console.log('Inside first function');
}
first();
console.log('Inside global execution context');

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

globalLexicalEnvironment = {
  EnvironmentRecord: {
      a     : 'Hello World!',
      first : <reference to function object >
  }
  outer: null
  ThisBinding: <Global Object>
}

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

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

functionLexicalEnvironment = {
  EnvironmentRecord: {
      b    : 25,
  }
  outer: <globalLexicalEnvironment>
  ThisBinding: <Global Object>
}

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

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

Подробный пример закрытия

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

Пример 1

См. код ниже:

function person() {
  let name = 'Peter';
  
  return function displayName() {
    console.log(name);
  };
}
let peter = person();
peter(); // prints 'Peter'

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

Таким образом, его лексическое окружение выглядит следующим образом:

personLexicalEnvironment = {
  EnvironmentRecord: {
    name : 'Peter',
    displayName: <displayName function reference>
  }
  outer: <globalLexicalEnvironment>
  ThisBinding: <Global Object>
}

из-заdisplayNameВ функции нет переменных, поэтому ее запись окружения будет пустой. Во время выполнения этой функции движок JavaScript попытается найти переменную в лексическом окружении этой функции.name.

из-заdisplayNameВ лексическом окружении функции нет переменных, поэтому она будет смотреть на внешнее лексическое окружение, т.е.personЛексическое окружение, в котором функция все еще находится в памяти. Движок JavaScript находит переменную иnameРаспечатать на консоль.

Пример 2

function getCounter() {
  let counter = 0;
  return function() {
    return counter++;
  }
}
let count = getCounter();
console.log(count());  // 0
console.log(count());  // 1
console.log(count());  // 2

такой же,getCounterЛексическое окружение функции выглядит так:

getCounterLexicalEnvironment = {
  EnvironmentRecord: {
    counter: 0,
    <anonymous function> : <reference to function>
  }
  outer: <globalLexicalEnvironment>
}

Эта функция возвращает анонимную функцию и назначает ееcountПеременная.

countПри выполнении его лексическое окружение будет выглядеть так:

countLexicalEnvironment = {
  EnvironmentRecord: {
  }
  outer: <getCountLexicalEnvironment>
}

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

Движок находит переменную, выводит ее на консоль и помещаетgetCounterПеременные в лексическом окружении функции увеличиваются на 1.

Поэтому первый звонокcountпосле функцииgetCounterЛексическое окружение функции выглядит так:

getCounterLexicalEnvironment = {
  EnvironmentRecord: {
    counter: 1,
    <anonymous function> : <reference to function>
  }
  outer: <globalLexicalEnvironment>
}

при каждом звонкеcountфункции, движок JavaScript создаст новое лексическое окружение для функции, увеличиваяcounterпеременная и обновлениеgetCounterЛексическое окружение функции отразить изменения.

В заключение

Благодаря приведенному выше объяснению, я полагаю, вы полностью поняли закрытие. Замыкания — это фундаментальная концепция JavaScript, которую должен понимать каждый разработчик JavaScript. Знакомство с этими понятиями поможет вам стать более эффективным и лучшим разработчиком JavaScript.

наконец

Прошлые основные моменты:

Подпишитесь на официальный аккаунт, чтобы увидеть больше.

公众号