→ Javascript замыкание функции. Подробнее про замыкания в JavaScript. Что подразумевает под собой замыкание

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 и зачем оно нужно. После этого выполним несколько практических примеров. В первом примере разберём, как происходит замыкание, а во втором - некоторую реальную задачу с использованием front-end фреймворка Bootstrap. В конце урока познакомимся с тем, как можно использовать замыкания для создания приватных переменных и функций.

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

В JavaScript функции могут находиться внутри других функций.

Когда одна функция находится внутри другой, то внутренняя функция имеет доступ к области видимости (окружению) внешней функции.

Этот способ организации кода в JavaScript позволяет создавать замыкания .

Рассмотрим пример создания замыкания :

Function outerF(numA) { var numB = 5; function innerF(numC) { return numA + numB + numC; } return innerF; } var result = outerF(3); console.log(result(7)); // 15 console.log(result(10)); // 18

В переменной result будет находиться результат выполнения функции outerF(3) , т.е. ссылка на функцию innerF .

Функции в JavaScript «запоминают» окружение , в котором они были созданы. Осуществляют это они посредством скрытого свойства [].

В этом примере функция innerF «запомнит» своё окружение, т.е. она в своём скрытом свойстве [] будет содержать ссылку на область видимости, в которой она была создана, в данном случае это outerF(3) .

В результате получилось замыкание (closure) , т.е. такое состояние в котором некоторая функция (в примере это innerF) имеет доступ к внешнему окружению, в данном случае другой функции outerF(3) , которая завершило уже своё выполнение.

Почему так происходит? В JavaScript для очистки памяти используется автоматический сборщик мусора. Он, после того как функция завершает своё выполнение, просматривает «окружение», которое было создано при её запуске и если на него нет ссылок, то он его уничтожает. В вышеприведённом примере этого не произошло, т.к. переменная result , находящаяся в глобальной области видимости, содержит ссылку на функцию innerF . А функция innerF содержит ссылку посредством своего скрытого свойства [] на «окружение» outerF(3) . В результате «окружение» outetF(3) не может быть удалено автоматическим сборщиком мусора даже несмотря на то, что функция уже завершила своё выполнение. Это происходит из-за того, что на «окружение» outerF(3) существует [] ссылка в функции innerF , а ссылка на innerF имеется в переменной result , находящейся в глобальной области видимости.

Для чего нужны замыкания? Замыкания, например, могут использоваться для «запоминания» параметров, защиты данных (инкапсуляции), привязывания функции к определённому контексту и др. Замыкания положены в основу многих паттернов (шаблонов для написания кода).

JavaScript - Замыкание на примере

Рассмотрим на примере, как происходит замыкание в JavaScript.

Объявим некоторую функцию, например f1 . Внутри этой функции объявим ещё одну функцию f2 (внутреннюю) и вернём её в качестве результата первой. Функция f1 пусть имеет параметр (переменную) x , а функция f2 - параметр (переменную) y . Функция f2 кроме доступа к параметру x имеет ещё доступ и к параметру y (по цепочки областей видимости).

//родительская функция для f2 function f1(x) { //внутренняя функция f2 по отношению к f1 function f2(y) { return x + y; } //родительская функция возвращает в качестве результата внутреннюю функцию return f2; }

Теперь перейдём к самому интересному, а именно рассмотрим, что произойдёт, если некоторой переменной c1 присвоить вызов функции f1(2) .

Var c1 = f1(2);

В результате выполнения функция f1(2) вернёт другую (внутреннюю) функцию f2 . Но, функция f2 в данном контексте позволяет получить значения переменных родительской функции (f1) даже несмотря на то, что функция f1 уже завершила своё выполнение.

Посмотрим детальную информацию о функции:

Console.dir(c1);

На изображение видно, что внутренняя функция запомнила окружение, в котором была создана. Она имеет доступ к переменной x родительской функции. Значение данной переменной (x) равно числу 2.

Теперь выведем в консоль значение функции c1(5) :

Console.log(c1(5));

Данная инструкция отобразит в консоли результат сложения значений параметров x и y . Значение x функция f2 будет брать из родительской области видимости.

Повторим вышепредставленные действия, но уже используя другую переменную (c2):

Var c2= f1(5); console.dir(c2); console.log(c2(5));

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

Итоговый js-код рассмотренного примера:

//родительская функция function f1(x) { //внутренняя функция f2 function f2(y) { return x + y; } //родительская функция возвращает в качестве результата внутреннюю функцию return f2; } var c1 = f1(2); var c2 = f1(5); //отобразим детальную информацию о функции c1 console.dir(c1); //отобразим детальную информацию о функции c2 console.dir(c2); console.log(c1(5)); //7 console.log(c2(5)); //10

Замыкания на практике

Замыкания в JavaScript являются очень интересной вещью. Они позволяют связать некоторые данные с функцией. Это очень похоже на то, как это реализовано в объекте, который позволяет связать свойства (переменные) и методы (действия над этими переменными). Такие задачи в веб-разработке попадаются очень часто. Давайте рассмотрим одну из подобных задач.

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

Кнопки, открывающие модальные окна:

Кнопка 1 Кнопка 2 Кнопка 3

Функция, возвращая в качестве результата другую функцию:

Function modalContent(idModal,idButton){ //переменная, содержащая код модального окна Bootstrap var modal=""+ ""+ ""+ ""+ "×"+ ""+ ""+ "Закрыть"+ ""; //инструкция, добавляющая HTML-код модального окна сразу после открывающего тега body $(modal).prependTo("body"); //связываем модальное окно с кнопкой: $("#"+idButton).click(function(){ $("#"+idModal).modal("show"); }); // функция modalContent возвращает в качестве результата другую функцию return function(modalTitle,modalBody) { //устанавливаем заголовок модальному окну $("#"+idModal).find(".modal-title").html(modalTitle); //устанавливаем модальному окну содержимое $("#"+idModal).find(".modal-body").html(modalBody); } }

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

$(function(){ //1 модальное окно var modal1 = modalContent("modal1","myButton1"); modal1("Заголовок 1","

Содержимое 1...

Содержимое 2...

Содержимое 3...

"); });

Итоговый код (кнопки + скрипт):

function modalContent(idModal,idButton){ var modal=""+ ""+ ""+ ""+ "×"+ ""+ ""+ "Закрыть"+ ""; $(modal).prependTo("body"); $("#"+idButton).click(function(){ $("#"+idModal).modal("show"); }); return function(modalTitle,modalBody) { $("#"+idModal).find(".modal-title").html(modalTitle); $("#"+idModal).find(".modal-body").html(modalBody); } } $(function(){ //1 модальное окно var modal1 = modalContent("modal1","myButton1"); modal1("Заголовок 1","

Содержимое 1...

"); //2 модальное окно var modal2 = modalContent("modal2","myButton2"); modal2("Заголовок 2","

Содержимое 2...

"); //3 модальное окно var modal3 = modalContent("modal3","myButton3"); modal3("Заголовок 3","

Содержимое 3...

"); }); Кнопка 1 Кнопка 2 Кнопка 3

Если необходимо изменить при наступлении каких-то событий заголовок и содержимое модального окна (например, второго), то это будет выглядеть так:

Modal2("Другой заголовок","

Другое содержимое...

");

Создание приватных методов посредством замыканий

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

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

HTML-код кнопок:

Кнопка 1 Кнопка 2

Функция, имеет приватную переменную _count и функцию (метод) incrementCount . Для управления приватными методами предназначены публичные методы (increment() и value), которая данная функция возвращает в качестве результата.

Var countButtonClick = function () { //приватная переменная var _count = 0; //приватная функция (увеличивает значение на 1) function incrementCount() { _count++; } //результат, который возвращает функция в результате своего выполнения return { increment: function() { incrementCount(); }, value: function() { return _count; } } }

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

$(function(){ var countClickBtn1 = countButtonClick(); var countClickBtn2 = countButtonClick(); $("#btn1").click(function(){ countClickBtn1.increment() console.log(countClickBtn1.value()); }); $("#btn2").click(function(){ countClickBtn2.increment() console.log(countClickBtn2.value()); }); });

Итоговый код:

Кнопка 1 Кнопка 2 var countButtonClick = function () { //приватная переменная var _count = 0; //приватная функция (увеличивает значение на 1) function incrementCount() { _count++; } //результат, который возвращает функция в результате своего выполнения return { increment: function() { incrementCount(); }, value: function() { return _count; } } } $(function(){ var countClickBtn1 = countButtonClick(); var countClickBtn2 = countButtonClick(); $("#btn1").click(function(){ countClickBtn1.increment() console.log(countClickBtn1.value()); }); $("#btn2").click(function(){ countClickBtn2.increment() console.log(countClickBtn2.value()); }); });

Переменные 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;

Замыкание - это комбинация функции и лексического окружения, в котором эта функция была определена.

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

Рассмотрим следующий пример:

Function init() { var name = "Mozilla"; // name - локальная переменная, созданная в init function displayName() { // displayName() - внутренняя функция, замыкание alert (name); // displayName() использует переменную, объявленную в родительской функции } displayName(); } init();

init() создаёт локальную переменную name и определяет функцию displayName() . displayName() - это внутренняя функция - она определена внутри init() и доступна только внутри тела функции init() . Обратите внимание, что функция displayName() не имеет никаких собственных локальных переменных. Однако, поскольку внутренние функции имеют доступ к переменным внешних функций, displayName() может иметь доступ к переменной name , объявленной в родительской функции 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; } }; })(); alert(Counter.value()); /* Alerts 0 */ Counter.increment(); Counter.increment(); alert(Counter.value()); /* Alerts 2 */ Counter.decrement(); alert(Counter.value()); /* Alerts 1 */

Тут много чего поменялось. В предыдущем примере каждое замыкание имело свой собственный контекст исполнения (окружение). Здесь мы создаем единое окружение для трех функций: Counter.increment , Counter.decrement , и Counter.value .

Единое окружение создается в теле анонимной функции, которая исполняется в момент описания. Это окружение содержит два приватных элемента: переменную privateCounter и фукцию changeBy(val) . Ни один из этих элементов не доступен напрямую, за пределами этой самой анонимной функции. Вместо этого они могут и должны использоваться тремя публичными функциями, которые возвращаются анонимным блоком кода (anonymous wrapper), выполняемым в той же анонимной функции.

Эти три публичные функции являются замыканиями, использующими общий контекст исполнения (окружение). Благодаря механизму lexical scoping в Javascript, все они имеют доступ к переменной privateCounter и функции changeBy .

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

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 */

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

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

Создание замыканий в цикле: Очень частая ошибка

До того, как в версии ECMAScript 6 ввели ключевое слово let , постоянно возникала следующая проблема при создании замыканий внутри цикла. Рассмотрим пример:

Helpful notes will appear here

E-mail:

Name:

Age:

function showHelp(help) { document.getElementById("help").innerHTML = help; } function setupHelp() { var helpText = [ {"id": "email", "help": "Ваш адрес e-mail"}, {"id": "name", "help": "Ваше полное имя"}, {"id": "age", "help": "Ваш возраст (Вам должно быть больше 16)"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } } setupHelp();

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

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

Проблема в том, что функции, присвоенные как обработчики события onfocus , являются замыканиями. Они состоят из описания функции и контекста исполнения (окружения), унаследованного от функции setupHelp . Было создано три замыкания, но все они были созданы с одним и тем же контекстом исполнения. К моменту возникновения события onfocus цикл уже давно отработал, а значит, переменная item (одна и та же для всех трех замыканий) указывает на последний элемент массива, который как раз в поле возраста.

В качестве решения в этом случае можно предложить использование функции, фабричной функции (function factory), как уже было описано выше в примерах:

Function showHelp(help) { document.getElementById("help").innerHTML = help; } function makeHelpCallback(help) { return function() { showHelp(help); }; } function setupHelp() { var helpText = [ {"id": "email", "help": "Ваш адрес e-mail"}, {"id": "name", "help": "Ваше полное имя"}, {"id": "age", "help": "Ваш возраст (Вам должно быть больше 16)"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = makeHelpCallback(item.help); } } setupHelp();

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

Соображения по производительности

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

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

Давайте рассмотрим не очень практичный, но показательный пример:

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

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

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

Методы вынесены в прототип. Тем не менее, переопределять прототип - само по себе является плохой привычкой, поэтому давайте перепишем всё так, чтобы новые методы просто добавились к уже существующему прототипу.

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; };

Код выше можно сделать аккуратнее:

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

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

Как известно, в JavaScript областью видимости локальных переменных (объявляемых словом var) является тело функции, внутри которой они определены.

Если вы объявляете функцию внутри другой функции, первая получает доступ к переменным и аргументам последней:

Код: function outerFn(myArg) {
var myVar;
function innerFn() {
//имеет доступ к myVar и myArg
}
}

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

Рассмотрим пример - функцию, возвращающую кол-во собственных вызовов:

Код: function createCounter() {
var numberOfCalls = 0;
return function() {
return ++numberOfCalls;
}
}
var fn = createCounter();
fn(); //1
fn(); //2
fn(); //3

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

Именно за эти свойства такие «вложенные» функции в JavaScript называют замыканиями (термином, пришедшим из функциональных языков программирования) - они «замыкают» на себя переменные и аргументы функции, внутри которой определены.

Применение замыканий

Упростим немножко пример выше - уберем необходимость отдельно вызывать функцию createCounter, сделав ее аномимной и вызвав сразу же после ее объявления:

Код: var fn = (function() {
var numberOfCalls = 0;
return function() {
return ++ numberOfCalls;
}
})();

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

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

Код: var createHelloFunction = function(name) {
return function() {
alert("Hello, " + name);
}
}
var sayHelloHabrahabr = createHelloFunction("Habrahabr");
sayHelloHabrahabr(); //alerts «Hello, Habrahabr»

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

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

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

Код: Function.prototype.bind = function(context) {
var fn = this;
return function() {
return fn.apply(context, arguments);
};
}
var HelloPage = {
name: "Habrahabr",
init: function() {
alert("Hello, " + this.name);
}
}
//window.onload = HelloPage.init; //алертнул бы undefined, т.к. this указывало бы на window
window.onload = HelloPage.init.bind(HelloPage); //вот теперь всё работает

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

Следующее, принципиально иное применение замыканий - защита данных (инкапсуляция) . Рассмотрим следующую конструкцию:

Код: (function() {

})();

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

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

Код: (function() {
//вышестоящее this сохранится
}).call(this);

Рассмотрим еще один прием из той же серии. Повсеместно популяризовали его разработчики фреймворка Yahoo UI, назвав его «Module Pattern» и написав о нем целую статью в официальном блоге.
Пускай у нас есть объект (синглтон), содержащий какие-либо методы и свойства:

Код: var MyModule = {
name: "Habrahabr",
sayPreved: function(name) {
alert("PREVED " + name.toUpperCase())
},
this.sayPreved(this.name);
}
}
MyModule.sayPrevedToHabrahabr();

С помощью замыкания мы можем сделать методы и свойства, которые вне объекта не используются, приватными (т.е. доступными только ему) :

Код: var MyModule = (function() {
var name = "Habrahabr";
function sayPreved() {
alert("PREVED " + name.toUpperCase());
}
return {
sayPrevedToHabrahabr: function() {
sayPreved(name);
}
}
})();
MyModule.sayPrevedToHabrahabr(); //alerts «PREVED Habrahabr»

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

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

Код: for (var i = 0; i < links.length; i++) {
alert(i);
}
}

На деле же оказывается, что при клике на любую ссылку выводится одно и то же число - значение links.length. Почему так происходит? В связи с замыканием объявленная вспомогательная переменная i продолжает существовать, при чем и в тот момент, когда мы кликаем по ссылке. Поскольку к тому времени цикл уже прошел, i остается равным кол-ву ссылок - это значение мы и видим при кликах.

Решается эта проблема следующим образом:

Код: for (var i = 0; i < links.length; i++) {
(function(i) {
links[i].onclick = function() {
alert(i);
}
})(i);
}

Здесь с помощью еще одного замыкания мы «затеняем» переменную i, создавая ее копию в его локальной области видимости на каждом шаге цикла. Благодаря этому все теперь работает как задумывалось.

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

зы
Для сохранений между вызовами проще использовать func_name.attr типа:

Код: function countIt(reset) {
if (reset ||! countIt.cnt) countIt.cnt = 0;
return countIt.cnt++;

 

 

Это интересно: