Javascript замыкание в цикле. Изучаем замыкания в JavaScript

Доброго времени суток, гики веб-разработки. Сегодня мы углубим ваши знания языка и разберем замыкания в JavaScript. Это очень важный, ключевой раздел при изучении JS, без которого по сути и «каши не сваришь».

Поэтому в текущей публикации мы пройдемся с вами по основным моментам замыкания для того, чтобы не быть чайниками. Я объясню, что это такое и с какими ошибками можно столкнуться. Также приведу несколько примеров для лучшего понимания материала. Как говорится: «Меньше слов, больше дела». Так что за дело!

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

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

Такое окружение называется лексической областью видимости – Lexical scoping . Если более простыми словами, то это механизм, который позволяет вложенным функциям использовать переменные, объявленные вовне их тела, и «замыкать» последние на себе.

Рассмотрим пример. В коде создается функция с названием IntCounter () , в которой объявляется локальная переменная calls и вложенная функция. Последняя должна возвращать количество вызовов в основном коде.

1 2 3 4 5 6 7 8 9 10 function IntCounter() { var calls = 0; return function() { return ++calls; } } var CountСalls = IntCounter (); CountСalls(); //1 CountСalls(); //2 CountСalls(); //3

function IntCounter() { var calls = 0; return function() { return ++calls; } } var CountСalls = IntCounter (); CountСalls(); //1 CountСalls(); //2 CountСalls(); //3

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

Именно поэтому в прикрепленном выше примере переменная calls продолжает свое существование и сохраняет последнее присвоенное значение.

Почему данный механизм возможен?

Вот тут будьте внимательны!!! Попытайтесь хорошенько разобрать и запомнить прочитанное. В будущем это поможет вам понимать более сложные вещи в .

Итак, ссылки на внешние переменные, объекты хранятся во внутреннем свойстве вложенной функции под названием [[ Scope]] . Это скрытое свойство, которое присваивается функциям при их создании и ссылается на их Lexical scoping .

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

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

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

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

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

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 Выберите размер шрифта
12 16 20

Том первый. Глава вторая.

Практический пример использования замыкания body { font-family: Arial, sans-serif; font-size: 14px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; } Выберите размер шрифта
12 16 20

Здесь написан какой-то текст исторического романа.

Том первый. Глава вторая.

Продолжение увлекательной истории...

function ChangeSize(newSize) { return function() { document.body.style.fontSize = newSize + "px"; }; } var size12 = ChangeSize(12); var size16 = ChangeSize(16); var size20 = ChangeSize(20); document.getElementById("size_12").onclick = size12; document.getElementById("size_16").onclick = size16; document.getElementById("size_20").onclick = size20;

Наиболее распространенные ошибки

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

Обычно новички пишут код следующим образом:

Помощник при изучении английских слов

Наведите на слово для получения перевода.

elections

electricity

electric

function showTranslation (translation) { document.getElementById("help").innerHTML = translation; } function DictionaryHelp() { var helpText = [ {"id": "1", "help": "Выборы"}, {"id": "2", "help": "Электричество"}, {"id": "3", "help": "Электрический"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onmouseover = function() { showTranslation (item.help); } } } DictionaryHelp();

Однако при запуске программы и наведении на любое из слов, ответ всегда будет один и тот же – перевод слова «электрический».

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

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

Для решения этой проблемы в новых версиях (начиная с ECMAScript 6 ) можно использовать ключевое let . В других ситуациях следует обратиться за помощью к function factory .

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

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

function showTranslation(translation) { document.getElementById("help").innerHTML = translation; } function HelpCallback(help) { return function() { showTranslation(help); }; } function DictionaryHelp() { var helpText = [ {"id": "1", "help": "Выборы"}, {"id": "2", "help": "Электричество"}, {"id": "3", "help": "Электрический"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onmouseover = HelpCallback(item.help); } } DictionaryHelp();

Переменные JavaScript могут принадлежать локальным или глобальном масштабе.

Глобальные переменные можно сделать локальными (частными) с замыканиями.

Глобальные переменные

Функция может получить доступ ко всем переменным, определенным внутри функции, например:

Пример

function myFunction() {
var a = 4;
return a * a;
}

Но функция также может получить доступ к переменным, определенным вне функции, например:

Пример

var a = 4;
function myFunction() {
return a * a;
}

В последнем примере a является глобальной переменной.

В веб-странице глобальные переменные принадлежат объекту Window.

Глобальные переменные могут быть использованы (и изменены) всеми скриптами на странице (и в окне).

В первом примере a - локальная переменная .

Локальную переменную можно использовать только внутри функции, где она определена. Он скрыт от других функций и другого кода скрипта.

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

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

Переменная продолжительность жизни

Глобальные переменные живут до тех пор, пока ваше приложение (ваше окно/ваша веб-страница) живет.

Локальные переменные имеют короткую жизнь. Они создаются при вызове функции и удаляются при завершении функции.

Встречная дилемма

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

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

Пример

// Initiate counter
var counter = 0;


function add() {
counter += 1;
}

// Call add() 3 times
add();
add();
add();

// The counter should now be 3

Проблема с решением выше: любой код на странице может изменить счетчик, без вызова Add ().

Счетчик должен быть локальным для функции Add (), чтобы предотвратить его изменение другим кодом:

Пример

// Initiate counter
var counter = 0;

// Function to increment counter
function add() {
var counter;
counter += 1;
}

// Call add() 3 times
add();
add();
add();

//The counter should now be 3. But it is 0

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

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

Пример

// Function to increment counter
function add() {
var counter;
counter += 1;
return counter;
}

// Call add() 3 times
add();
add();
add();

//The counter should now be 3. But it is 1.

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

Внутренняя функция JavaScript может решить эту проблему.

Вложенные функции JavaScript

Все функции имеют доступ к глобальной области видимости.

В самом деле, в JavaScript, все функции имеют доступ к области "выше" их.

JavaScript поддерживает вложенные функции. Вложенные функции имеют доступ к области "выше" их.

В этом примере внутренняя функция Plus () имеет доступ к переменной Counter в родительской функции:

Пример

function add() {
var counter = 0;
function plus() {counter += 1;}
plus();
return counter;

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

Концепция замыканий

Закрытия были разработаны в 1960-х годах для механической оценки выражений в исчислении и применены в 1970 году как особенность языка программирования PAL для поддержки функций первого класса с лексической сферой. Питер Ландин дал определение термину "замыкание" в 1964 году со средой и контрольной частью, применяемых на машине SECD с целью оценки лямбда-выражений, связанных лексической средой, что приводило к закрытию их или замыканию в Javascript.

Такое объяснение вошло в 1975 году как лексически ограниченный вариант LISP и стало широко распространенным. Лексическая среда является множеством действительных переменных в программе. Она состоит из внутренней лексической среды и ссылок на внешнюю среду, называемую нелокальными переменными.

Лексические замыкания в Javascript являются функциями с ее внешней средой. Как и в JavaScript, все переменные имеют ссылку на тип. JS использует только привязку по ссылке - которая соответствует в C ++ 11, а время жизни нелокальных переменных, захваченных функцией, распространяется на время жизни функции.

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

В этом примере выражение lambda (lambda (book) (>= (book-sales book) threshold)) появляется внутри функции best-selling-books. Когда вычисляется лямбда-выражение, схема создает замыкание, состоящее из кода для выражения лямбда и ссылки на threshold переменную, которая является свободной переменной внутри выражения лямбда. Замыкание затем передается filter функции, которая вызывает ее неоднократно, чтобы определить, какие книги должны быть добавлены в список результатов и которые должны быть отброшены.

Поскольку тут замыкание в значении threshold, последняя может использовать ее каждый раз, когда ее filter вызывает. Сама функция filter может быть определена в совершенно отдельном файле. Вот тот же пример, переписанный в JS. Он демонстрирует, как работают замыкания под капотом в Javascript.

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

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

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

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

  • Их можно использовать для определения структур управления. Например, все стандартные структуры управления Smalltalk, включая ветви (if / then / else) и циклы (while и for), определяются с использованием объектов, методы которых принимают замыкания. Пользователи также могут легко использовать замыкания для определения структуры управления. В языках, реализующих назначение, можно создавать ее многофункциональную среду, позволяя общаться конфиденциально и изменять эту среду. Замыкание используется для реализации объектных систем.
  • Создание как частных, так и общедоступных методов переменных, используя шаблоны модуля. Из-за того, что возвращаемые функции наследуют область родительской функции, они доступны всем переменным и аргументам в данном контексте.
  • Оно полезно в ситуации, когда функция использует один и тот же ресурс для каждого вызова, но и создает сам ресурс для него. Это обстоятельство делает метод неэффективным, которое устраняется исключительно замыканием.
  • Согласно MDN (Mozilla Developer Network) «Closures - это функции с независимыми переменными, которые «запоминают» среду своего создания». И, как правило, когда функция завершается, ее локальные переменные больше не существуют. Понять, как работают замыкание в Javascript, можно рассмотрев несколько механизмов. Первый - формальная логика. Например, применив функцию logName, которая принимает одно имя в качестве параметра и регистрирует его. Затем создаю цикл for, чтобы перебирать список имен, задавать 1-й тайм-аут, а затем вызывать функцию logName, проходящую в текущем имени.

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

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

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

    • var x = 3;
    • y = 5;
    • var z = x + y.

    Или если не намерены повторно обработать номера:var z = 3 + 5;

    Это и есть анонимные номера. Для анонимных функций можно объявить их, когда их используют «на лету» - без прохождения переменной. Например, взять функцию do из ранее:

    { alert("Ceci est une fonction anonyme.");

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

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

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

    var tab = ; var tab2 = tab.

    Где, tab и tab2 - две ссылки на одну и ту же таблицу, технически это указатели, управляемые сборщиком мусора. Функции также передаются по ссылке. Переменная globalFn больше не скрыта. Порядок позволяет это делать, что продемонстрировано на примере задачи на замыкание Javascript.

    Вот как можно извлечь функцию из локального контекста, если функция удовлетворяет другим локальным переменным. Простой пример: auto-increment, функция, которая возвращает целое число, которое увеличивается на 1 при каждом вызове. Конкретно, нужна функция inc, которая ведет себя следующим образом:

    // retourne 0 inc();

    // retourne 1 inc();

    // retourne 2 inc();

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

    function makeInc() { var x = 0; return function() { return x++; } } var inc = makeInc();

    В последней строке в тот момент, когда создается переменная функция inc, она несет в себе какие-то переменные, которые есть вокруг, в этом случае x. Он создает некий невидимый объект вокруг функции, который содержит эту переменную. Этот объект является функцией замыкания Javascript. При этом каждая копия функции будет иметь свое замыкание:

    var inc1 = makeInc();

    var inc2 = makeInc();

    Как видно, замыкание очень полезно во многих случаях.

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

    Естественно, A.x и B.x это не одна и та же переменная. Однако если просто нужно запустить скрипт, не требуя сохранения переменных для остальных, можно использовать анонимную функцию, как замыкание. Это дает несколько странный синтаксис. Хотя две строки кода в середине довольно обычны, с другой стороны, функция, которая находится вокруг, выполняется «на лету». Обращают внимание на круглые скобки ()в конце. И чтобы иметь возможность делать замыкание, анонимная функция сама должна быть окружена круглыми скобками.

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

    Существует вариант: (function() {// ...}());

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

    Javascript-программирование в циклах

    Когда пользователь выполняет большие объемы Javascript-программирования, ему трудно избежать циклов. Кого-то это сводит с ума, после чего они приходят к мысли, что всякая реализация Javascript имеет серьезную ошибку. Если у разработчика уже есть цикл, который он не хочет преобразовывать, чтобы использовать функцию итератора, все, что ему нужно сделать, - это замыкание, в котором он определяет новые переменные. Они фиксируют текущее значение переменных, и изменяющихся на каждой итерации. Уловкой для захвата переменных является то, что внешнее замыкание выполняется сразу же во время текущей итерации цикла. Можно использовать один из этих двух примерных подходов

    Теперь есть еще одно упрощенное решение этой проблемы, поскольку let ключевое слово поддерживается как в Firefox, так и в Chrome. Оно является ключевым слово вместо var переменного блока. Let работает магическим образом, потому что объявляется новую переменную j, значение i которой фиксируется замыканием внутри цикла. Однако надо учитывать, что оно не продолжает существовать после конца одной итерации цикла, поскольку оно локально.

    Петля и функция

    For Цикл в JavaScript не представляется, так же как for цикл в C или Java. На самом деле это больше похоже на PHP. Самое главное знание о циклах в JS заключается в том, что они не создают область действия. JS не имеет блок сферы, только функцию объема. Это свойство можно рассмотреть на следующем фрагменте:

    function foo() {var bar = 1;

    for(var i = 0; i< 42; i++) {var baz = i;} /* more code */}

    Понятно, что bar доступно во всей функции. До первой итерации цикла baz будет иметь значение undefined. После цикла он будет иметь значение 41 (и i будет 42). Таким образом, всякая переменная, объявленная в любом месте функции, будет доступна везде в функции и будет иметь значение только после того, как она была назначена ему.

    Затворы и агрегирование

    Замыкание - это не что иное, как функции, внутри других функций, и передаются в какой-то другой контекст. Они называются замыканием, так как они закрывают через локальные переменные, то есть доступны к другим функциям сферы. Например, время, x определенное как параметр foo, и var bar = foo(2)() вернется 84.

    Возвращаемая функция foo имеет доступ x. Это все важно, потому что помогает разработчикам создавать функции внутри циклов, зависящих от переменных цикла. Рассмотрим этот фрагмент, который присваивает click-обработчик различным элементам:

    // elements is an array of 3 DOM elements var values = ["foo", "bar", "baz"];

    i< l; i++) {var data = values[i];

    elements[i].onclick = function() {alert(data);

    Значение, которое они будут использовать alert при нажатии, будет одинаково для всех, а именно baz. К тому времени вызывается обработчик событий, for уже завершен. JS не имеет области блока, т.е. все обработчики используют ссылку на одну и ту же data переменную. После петли, это значение будет values. Каждое объявление переменной создает одно место в памяти хранения данных. В for эти данные снова и снова меняются, положение в памяти остается неизменным.

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

    function createEventHandler(x) {return function() {alert(x);

    for(var i = 0, l = elements.length;

    i< l; i++) {var data = values[i];

    elements[i].onclick = createEventHandler(data);

    Это работает, потому что значение data будет храниться в локальной области, createEventHandler и эта функция выполняется на каждой итерации. Это можно записать короче, используя сразу исполняемые функции:

    for(var i = 0, l = elements.length;

    i< l; i++) {var data = values[i];

    elements[i].onclick = (function(x) {function() {alert(x);

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

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

    function work(name){

    return function (topic) {

    console.log(What is ${topic} in ${name});

    work("Javascript")("Closure");

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

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

    Пример внутренней функции

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

    Таким образом, в переменной внешнего окружения все еще существует так, что анонимная функция имеющая доступ к переменной имени печатает в консоли, например, «Что такое замыкание в Javascript ». Внутренняя анонимная функция //main.js

    function factory(){ var products = ;

    i++){ products.push(function () { console.log(i);

    } return products;

    } var soap = factory();

    Результат этого примера довольно незначителен и равен 2.

    Когда мыло - soap () называется внешней переменной контекста, всегда 2, потому что в цикле условие ложно в i