Поддержка  •  Дневник  •  Без рекламы  •  О сайте  •  Реклама  •  Поставить баннер  •  Fleshlight  •  Прислать  •  Хроника  •  Translate Гости: 5    Участники: 0 Авторизация Авторизация   Регистрация 
Метод Научного Тыка
RULVEN
Поиск  
Blackball iMag | интернет-журнал
Каталог


Начало » Разработка ПО » Замыкания в JavaScript для начинающих
Мне повезёт!

Замыкания в JavaScript для начинающих


Добавлено: Пн 05.04.2021 • Sergeant
Источник: источник
Просмотров: 668
Комментарии: 0


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

Материал, перевод которого мы публикуем сегодня, посвящён рассказу о внутренних механизмах замыканий и о том, как они работают в JavaScript-программах.

 

Что такое замыкание?

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

Что такое лексическое окружение?

Понятие «лексическое окружение» или «статическое окружение» в JavaScript относится к возможности доступа к переменным, функциям и объектам на основе их физического расположения в исходном коде. Рассмотрим пример:

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

Здесь у функции inner() есть доступ к переменным, объявленным в её собственной области видимости, в области видимости функции outer() и в глобальной области видимости. Функция outer() имеет доступ к переменным, объявленным в её собственной области видимости и в глобальной области видимости.

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

Global {
  outer {
    inner
  }
}

Обратите внимание на то, что функция inner() окружена лексическим окружением функции outer(), которая, в свою очередь, окружена глобальной областью видимости. Именно поэтому функция inner() может получить доступ к переменным, объявленным в функции outer() и в глобальной области видимости.

Практические примеры замыканий

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

Пример №1

function person() {
  let name = 'Peter';
 
  return function displayName() {
    console.log(name);
  };
}
let peter = person();
peter(); // '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() представляет собой замыкание, она может обращаться к переменной counter функции getCount() даже после того, как функция getCounter() завершила работу.

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

Всё работает именно так из-за того, что при каждом вызове функции count() для неё создаётся новая область видимости, но существует лишь одна область видимости для функции getCounter(). Так как переменная counter объявлена в области видимости функции getCounter(), её значение между вызовами функции count() сохраняется, не сбрасываясь в 0.

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

До сих пор мы говорили о том, что такое замыкания, и рассматривали практические примеры. Теперь поговорим о внутренних механизмах JavaScript, обеспечивающих их работу.

Для того чтобы понять замыкания, нам нужно разобраться с двумя важнейшими концепциями JavaScript. Это — контекст выполнения (Execution Context) и лексическое окружение (Lexical Environment).

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

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

В некий момент времени может выполняться код лишь в одном контексте выполнения (JavaScript — однопоточный язык программирования). Управление этими процессами ведётся с использованием так называемого стека вызовов (Call Stack).

Стек вызовов — это структура данных, устроенная по принципу LIFO (Last In, First Out — последним вошёл, первым вышел). Новые элементы можно помещать только в верхнюю часть стека, и только из неё же элементы можно изымать.

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

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

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

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

Стек вызовов этого кода выглядит так:

Стек вызовов

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

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

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

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

Лексическое окружение содержит два компонента:

  • Запись окружения (environment record) — место, где хранятся объявления переменных и функций.
  • Ссылка на внешнее окружение (reference to the outer environment) — ссылка, позволяющая обращаться к внешнему (родительскому) лексическому окружению. Это — самый важный компонент, с которым нужно разобраться для того, чтобы понять замыкания.

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

lexicalEnvironment = {
  environmentRecord: {
    : ,
    :
  }
  outer: < Reference to the parent lexical environment>
}

Взглянем на следующий фрагмент кода:

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

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

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

Обратите внимание на то, что ссылка на внешнее лексическое окружение (outer) установлена в значение null, так как у глобальной области видимости нет внешнего лексического окружения.

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

functionLexicalEnvironment = {
  environmentRecord: {
      b    : 25,
  }
  outer:
}

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

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

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

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

Пример №1

Взгляните на данный фрагмент кода:

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

Когда выполняется функция person(), JS-движок создаёт новый контекст выполнения и новое лексическое окружение для этой функции. Завершая работу, функция возвращает функцию displayName(), в переменную peter записывается ссылка на эту функцию.

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

personLexicalEnvironment = {
  environmentRecord: {
    name : 'Peter',
    displayName: < displayName function reference>
  }
  outer:
}

Когда функция person() завершает работу, её контекст выполнения извлекается из стека. Но её лексическое окружение остаётся в памяти, так как ссылка на него есть в лексическом окружении её внутренней функции displayName(). В результате переменные, объявленные в этом лексическом окружении, остаются доступными.

Когда вызывается функция peter() (соответствующая переменная хранит ссылку на функцию displayName()), JS-движок создаёт для этой функции новый контекст выполнения и новое лексическое окружение. Это лексическое окружение будет выглядеть так:

displayNameLexicalEnvironment = {
  environmentRecord: {
    
  }
  outer:
}

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

Так как в лексическом окружении функции displayName() искомое найти не удаётся, поиск продолжится во внешнем лексическом окружении, то есть, в лексическом окружении функции person(), которое всё ещё находится в памяти. Там движок находит нужную переменную и выводит её значение в консоль.

Пример №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,
    : < reference to function>
  }
  outer:
}

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

Когда выполняется функция count(), её лексическое окружение выглядит так:

countLexicalEnvironment = {
  environmentRecord: {
 
  }
  outer:
}

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

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

В результате лексическое окружение функции getCounter() после первого вызова функции count() будет выглядеть так:

getCounterLexicalEnvironment = {
  environmentRecord: {
    counter: 1,
    : < reference to function>
  }
  outer:
}

При каждом следующем вызове функции count() JavaScript-движок создаёт новое лексическое окружение для этой функции и инкрементирует переменную counter, что приводит к изменениям в лексическом окружении функции getCounter().

Итоги

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

 



Мне нравится 0   Мне не нравится 0



Комментарии

Чтобы добавить видео с YouTube, нужно написать [@youtube=xxxxx] , где xxxxx – ID видео.


Комментарии: 0
Нет ни одного комментария.
RSS-лента
Поделиться ссылкой:
Что приготовить из курицы: 20 быстрых и вкусных рецептов Что приготовить из курицы: 20 быстрых и вкусных рецептов
Удаление грудей
12 быстрых рецептов подливы к рису
Напитки новой эры: лучшие американские и русские горящие коктейли Напитки новой эры: лучшие американские и русские горящие коктейли
Безопасен ли анальный секс во время беременности? Безопасен ли анальный секс во время беременности?
Свинг - обмен с возвратом Свинг - обмен с возвратом
Расход топлива. Как сделать свой автомобиль более экономным?
Штрудель с вишней из слоёного теста Штрудель с вишней из слоёного теста
Лазерные фары: что это и как это работает? Лазерные фары: что это и как это работает?
Рекомендованные решения по кинотеатральным системам Рекомендованные решения по кинотеатральным системам

Новое
Soft skills: 18 самых важных навыков, которыми должен владеть каждый работник 3 дня назад, 09:08
Soft skills: 18 самых важных навыков, которыми должен владеть каждый работник
30 вопросов на собеседовании фронтенд разработчика Пн 15.04.2024
30 вопросов на собеседовании фронтенд разработчика
15 соусов для креветок, которые ты захочешь приготовить Сб 13.04.2024
15 соусов для креветок, которые ты захочешь приготовить
10 простых рецептов рыбы в кляре Пт 12.04.2024
10 простых рецептов рыбы в кляре
2 простых рецепта алкогольного пунша в домашних условиях Пт 12.04.2024
2 простых рецепта алкогольного пунша в домашних условиях
20 рецептов соуса барбекю, которые готовятся проще простого Пт 12.04.2024
20 рецептов соуса барбекю, которые готовятся проще простого
8 лучших рецептов мегрельского соуса Пт 12.04.2024
8 лучших рецептов мегрельского соуса
15 лёгких рецептов соуса Унаги в домашних условиях Пт 12.04.2024
15 лёгких рецептов соуса Унаги в домашних условиях
Ср 10.04.2024
Секреты здоровых ногтей: Уход и питание для красивого маникюра
High performance object-oriented data access with Dapper Ср 10.04.2024
High performance object-oriented data access with Dapper
Книги
Building IoT Visualizations using Grafana Вт 09.04.2024
Building IoT Visualizations using Grafana
Год: 2022
Getting Started with Grafana Вт 02.04.2024
Getting Started with Grafana
Год: 2022
Prometheus: Up & Running Вт 26.03.2024
Prometheus: Up & Running
Год: 2018

Разработано на основе BlackNight CMS
Release v.2024-04-19
© 2000–2024 Blackball
Дизайн & программирование:
О сайтеРеклама
Visitors
Web-site performed by Sergey Drozdov
BlackballРекламаСтатистикаПоддержка | МузыкаПлейлистыКиноВидеоИгрыАудиоПрограммыСтатьиКартинкиЮморФорумДневник сайтаПрислать контент