Javascript замыкание функции. JavaScript: область видимости и замыкание. Практические примеры замыкания

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment ). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

Lexical scoping

Consider the following:

Function init() { var name = "Mozilla"; // name is a local variable created by init function displayName() { // displayName() is the inner function, a closure alert(name); // use variable declared in the parent function } displayName(); } init();

init() creates a local variable called name and a function called displayName() . The displayName() function is an inner function that is defined inside init() and is only available within the body of the init() function. Note that the displayName() function has no local variables of its own. However, since inner functions have access to the variables of outer functions, displayName() can access the variable name declared in the parent function, init() .

Var counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } }; })(); console.log(counter.value()); // logs 0 counter.increment(); counter.increment(); console.log(counter.value()); // logs 2 counter.decrement(); console.log(counter.value()); // logs 1

In previous examples, each closure has had its own lexical environment. Here, though, we create a single lexical environment that is shared by three functions: counter.increment , counter.decrement , and counter.value .

The shared lexical environment is created in the body of an anonymous function, which is executed as soon as it has been defined. The lexical environment contains two private items: a variable called privateCounter and a function called changeBy . Neither of these private items can be accessed directly from outside the anonymous function. Instead, they must be accessed by the three public functions that are returned from the anonymous wrapper.

Those three public functions are closures that share the same environment. Thanks to JavaScript"s lexical scoping, they each have access to the privateCounter variable and changeBy function.

You"ll notice we"re defining an anonymous function that creates a counter, and then we call it immediately and assign the result to the counter variable. We could store this function in a separate variable makeCounter and use it to create several counters.

Var makeCounter = function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } }; var counter1 = makeCounter(); var counter2 = makeCounter(); alert(counter1.value()); /* Alerts 0 */ counter1.increment(); counter1.increment(); alert(counter1.value()); /* Alerts 2 */ counter1.decrement(); alert(counter1.value()); /* Alerts 1 */ alert(counter2.value()); /* Alerts 0 */

Notice how each of the two counters, counter1 and counter2 , maintains its independence from the other. Each closure references a different version of the privateCounter variable through its own closure. Each time one of the counters is called, its lexical environment changes by changing the value of this variable; however changes to the variable value in one closure do not affect the value in the other closure.

Using closures in this way provides a number of benefits that are normally associated with object-oriented programming -- in particular, data hiding and encapsulation.

Closure Scope Chain

For every closure we have three scopes:-

  • Local Scope (Own scope)
  • Outer Functions Scope
  • Global Scope

So, we have access to all three scopes for a closure but often make a common mistake when we have nested inner functions. Consider the following example:

// global scope var e = 10; function sum(a){ return function(b){ return function(c){ // outer functions scope return function(d){ // local scope return a + b + c + d + e; } } } } console.log(sum(1)(2)(3)(4)); // log 20 // We can also write without anonymous functions: // global scope var e = 10; function sum(a){ return function sum2(b){ return function sum3(c){ // outer functions scope return function sum4(d){ // local scope return a + b + c + d + e; } } } } var s = sum(1); var s1 = s(2); var s2 = s1(3); var s3 = s2(4); console.log(s3) //log 20

So, in the example above, we have a series of nested functions all of which have access to the outer functions" scope, but which mistakenly guess only for their immediate outer function scope. In this context, we can say all closures have access to all outer function scopes within which they were declared.

Creating closures in loops: A common mistake function showHelp(help) { document.getElementById("help").innerHTML = help; } function setupHelp() { var helpText = [ {"id": "email", "help": "Your e-mail address"}, {"id": "name", "help": "Your full name"}, {"id": "age", "help": "Your age (you must be over 16)"} ]; helpText.forEach(function(text) { document.getElementById(text.id).onfocus = function() { showHelp(text.help); } }); } setupHelp(); Performance considerations

It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.

For instance, when creating a new object/class, methods should normally be associated to the object"s prototype rather than defined into the object constructor. The reason is that whenever the constructor is called, the methods would get reassigned (that is, for every object creation).

Consider the following case:

Function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; }

Because the previous code does not take advantage of the benefits of using closures in this particular instance, we could instead rewrite it to avoid using closure as follows:

Function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype = { getName: function() { return this.name; }, getMessage: function() { return this.message; } };

However, redefining the prototype is not recommended. The following example instead appends to the existing prototype:

Function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; };

In the two previous examples, the inherited prototype can be shared by all objects and the method definitions need not occur at every object creation. See

Доброго времени суток, гики веб-разработки. Сегодня мы углубим ваши знания языка и разберем замыкания в 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, в чем многие испытавают трудности.

Введение

В сети довольно много статей, в которых пытаются объяснить области видимости и замыкания, но в общем, я бы сказал, что большинство из них не совсем понятны. Кроме того, в некоторых статьях предполагается, что вы программировали до этого на 15 других языках, хотя как я считаю - большинство людей пишущих на JavaScript имеют лишь опыт в HTML и CSS, а не в C или Java.

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

Область видимости

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

Глобальная область видимости

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

var monkey = "Gorilla"; function greetVisitor () { return alert("Hello dear blog reader!"); }

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

Локальная область видимости

В отличие от глобальной области видимости, локальная область видимости - это когда что-то определено и доступно только в некоторой части кода, как например функция. Рассмотрим пример:

function talkDirty () { var saying = "Oh, you little VB lover, you"; return alert(saying); } alert(saying); // Throws an error

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

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

function saveName (firstName) { function capitalizeName () { return firstName.toUpperCase(); } var capitalized = capitalizeName(); return capitalized; } alert(saveName("Robert")); // Returns "ROBERT"

Как вы только что увидели, внутренней функции capitalizeName не нужно передавать никаких параметров, т.к. она имеет полный доступ к параметру firstName во внешней функции saveName. Для большей ясности, рассмотрим еще один пример:

function siblings () { var siblings = ["John", "Liza", "Peter"]; function siblingCount () { var siblingsLength = siblings.length; return siblingsLength; } function joinSiblingNames () { return "I have " + siblingCount() + " siblings:nn" + siblings.join("n"); } return joinSiblingNames(); } alert(siblings()); // Outputs "I have 3 siblings: John Liza Peter"

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

Замыкание

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

function add (x) { return function (y) { return x + y; }; } var add5 = add(5); var no8 = add5(3); alert(no8); // Returns 8

Вот это да! Что здесь происходит? Давайте разбираться:

1. Когда мы вызываем функцию add, она возвращает функцию.

2. Эта функция закрывает контекст и запоминает, каким был параметр x в это время (т.е. в данном случае значением 5)

3. Когда результат функции add присваивается переменной add5, она всегда будет знать, каким был x при создании этой переменной.

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

5. Это означает, что когда мы вызываем add5 со значением 3, она сложит числа 5 и 3, и вернет 8.

На самом деле, в мире JavaScript, функция add5 выглядит следующим образом:

function add5 (y) { return 5 + y; }

Пресловутая проблема циклов
Сколько раз вы создавали циклы, в которых хотели присвоить значение i каким-либо образом, например элементу, и понимали, что возвращается лишь последнее значение i?

Неправильное обращение

Давайте посмотрим на этот некорректный код, который создает 5 элементов , добавляет значение i как текст к каждому элементу и onclick, который как ожидается будет выдавать alert со значением i для данной ссылки, т.е. то же самое значение, что и в тексте элемента. Затем элементы добавляются к document body: