В данном уроке мы разберем анонимные функции и замыкания на языке JavaScript.
Режим use strict
В JavaScript существует специальный режим, по которому код будет выполнятся по современному стандарту ES5, а не по более старому (это по умолчанию).
Этот режим включается директивой "use strict"; или 'use strict'; и ставится в начале скрипта:
"use strict";
тут код
Строгий режим можно активировать только для кода функции:
function func() {
"use strict";
};
Исходный код функции и результат
Пусть у нас есть какая-то функция. Напоминаю вам, что есть вызвать эту функцию с круглыми скобками - вы увидите результат работы этой функции, а если без круглых скобок - ее исходный код:
function func() {
return '!';
});
alert(func()); //результат
alert(func); //исходный код функции
Функция как переменная
В JavaScript функции - это такие же переменные. К примеру, если у нас есть какая-то функция - мы можем записать ее в другую переменную - и эта функция станет доступна с другим именем:
function func() {
alert('!');
});
var test = func;
test(); //выведет '!'
func(); //выведет '!'
Можно также создать безымянную функцию и записать ее в какую-то переменную:
var func = function() {
alert('!');
});
func();
Так как функция - это переменная, то невозможно существование переменной и функции с одинаковым именем. В следующем примере функция func будет затерта и вместо нее станет строка 'hello':
function func() {
return '!';
});
var func = 'hello';
func(); //получим ошибку
Функциональные выражения и объявление функций
Функцию можно объявить двумя способами: первый способ вы изучали в предыдущих уроках, а второй способ - это сделать безымянную функцию и записать ее в какую-либо переменную:
//Первый способ:
function func() {
alert('!');
});
//Второй способ:
var func = function() {
alert('!');
});
Первый способ называется Function Declaration (объявление функции), а второй - Function Expression (функциональное выражение):
//Function Declaration:
function func() {
alert('!');
});
//Function Expression:
var func = function() {
alert('!');
});
По сути это одно и то же, но есть существенная разница: функции, объявленные как Function Declaration, создаются до выполнения кода. Поэтому их можно вызвать до объявления, например:
func(); //выведет '!'
function func() {
alert('!');
});
А функциональные выражения создаются в момент выполнения кода и недоступны выше. Поэтому такой код выдаст ошибку:
func(); //ошибка, такой функции еще нет!
var func = function () {
alert('!');
});
Функциональные выражения
Функциональные выражения достаточно удобная штука - можно, например, записать в переменную одну из функций, в зависимости от условия:
"use strict";
var bool = true;
if (bool) {
var func = function () {
alert('!');
}
} else {
var func = function () {
alert('!!');
}
}
func();
Особенности Function Declaration при use strict
Function Declaration при use strict видны только внутри блока, в котором объявлены. В данном случае функция func объявлена внутри ифа, но не будет видна снаружи:
"use strict";
var bool = true;
if (bool) {
function func() {
alert('!');
}
}
func(); //выдаст ошибку - тут функция не видна!
Без use strict все будет нормально.
Передача функции по ссылке
Функции в JavaScript - это объекты. А объекты в JavaScript копируются по ссылке - это значит, что если у нас есть две переменные с одной и той же функцией - в них не лежит копия этой функции, а обе эти переменные ссылаются на одну и ту же функцию:
function func() {
alert('!');
});
var test = func; //И test и func указывают на одну и ту же функцию
Анонимные функции
Функции, не имеющие имени, называются анонимными. Такие функции бывают полезны в тех случаях, когда имя функции нам без надобности и нет смысла тратить время на придумывание этого имени.
Пример: пусть у нас есть переменная elem, в которой лежит ссылка на какой-то элемент. Привяжем в качестве события onclick анонимную функцию:
elem.onclick = function() {
alert('!');
});
Тоже самое при addEventListener:
elem.addEventListener('click', function() {
alert('!');
});
Впрочем, если имя функции вам нужно, например, для того, чтобы открепить ее через removeEventListener - можно дать и имя:
elem.addEventListener('click', function show() {
this.removeEventListener('click', show);
});
Это имя будет доступно только внутри самой функции - и нигде извне.
Функция как параметр другой функции
Пусть у нас даны 2 функции:
var get1 = function() {
return 1;
}
var get2 = function() {
return 2;
}
Сделаем третью функцию go, которая будет ожидать, что первым и вторым параметром ей передаются функции, которые возвращают какие-то числа.
Наша функция go запишет первую функцию в переменную func1, а вторую - в func2, затем просуммирует числа, возвращаемые этими функциями и выведет их на экран:
function go(func1, func2) {
alert(func1() + func2());
}
go(get1, get2); //выведет 3
Получается, что в функцию можно передавать параметрами другие функции, записывать их в переменные и использовать внутри.
Рассмотрим еще пример: сейчас в функцию и go будем передавать параметром разные функции - и будем видеть разный результат:
function show1() {
alert('!');
});
function show2() {
alert('!!');
});
function go(func) {
func();
});
go(show1); //выведет '!'
go(show2); //выведет '!!'
А можем вообще использовать анонимную функцию, создаваемую в момент передачи параметра:
function go(func) {
func();
});
go(function() {
alert('!');
});
Функция в функции
Одну функцию можно объявить внутри другой. В этом случае внутренняя функция не будет доступна извне:
function func() {
function test() {
alert('!');
};
});
test(); //выдаст ошибку
Зачем это нужно: внутренняя функция может быть использована как вспомогательная и это удобно, что она не доступна извне - мы не захламляем глобальную область видимости лишними функциями.
Функция, возвращающая функцию
Функция может возвращать другую функцию, например так:
function func() {
return function() {
return '!';
};
};
В этом случае, если мы посмотрим результат работы внешней функции - мы увидим исходный код внутренней функции:
function func() {
return function() {
return '!';
};
}
alert( func() ); //увидим код внутренней функции
Чтобы увидеть результат работы внутренней функции - нужно вызвать внешнюю функции с двумя круглыми скобками:
function func() {
return function() {
return '!';
};
}
alert( func()() ); //увидим '!'
Могут быть и такие вызовы функций: func()()() и func()()()() - и так далее до бесконечности. Для этого нужно, чтобы внутренняя функция тоже возвращала функцию, та - еще одну и так далее.
Область видимости переменных
Давайте вспомним про область видимости переменных. Напомню, что внешняя переменная видна внутри функции:
var num = 5;
function test() {
//Внешняя переменная num видна внутри функции:
alert(num);
};
test(); //выведет 5
То же самое будет, если у нас функция содержит внутри другую функцию - переменные внешней функции видны во внутренней:
function func() {
var num = 5;
function test() {
//Внешняя переменная num видна внутри функции:
alert(num);
};
test();
};
func(); //выведет 5
Замыкания
Пусть у нас есть переменная num, определенная снаружи функции:
var num = 0;
function func() {
num++;
return num;
}
Если вызвать нашу функцию - то она сначала увеличит переменную num на единицу, а затем вернет новое значение num:
var num = 0;
function func() {
num++;
return num;
}
alert(func()); //выведет 1
Если вызвать нашу функцию несколько раз - каждый раз она будет выводить на единицу больше, так как каждый вызов func приводит к увеличению внешней переменной num:
var num = 0;
function func() {
num++;
return num;
}
alert(func()); //выведет 1
alert(func()); //выведет 2
alert(func()); //выведет 3
alert(func()); //выведет 4
alert(func()); //выведет 5
В нашем примере есть недостаток - переменная num видна во всем скрипте. Любая другая функция может случайно затереть num - и вся наша конструкция перестанет работать правильно. Давайте реализуем более продвинутый счетчик, избавленный от этого недостатка.
Обернем всю нашу конструкцию в функцию (назовем ее createCounter), а функцию func, которая у нас была ранее, сделаем анонимной и сделаем так, чтобы новая функция createCounter возвращала эту анонимную функцию:
function createCounter() {
var num = 0;
return function() {
num++;
return num;
};
}
var counter = createCounter();
Рассмотрим подробнее, что тут происходит: переменная num является локальной внутри функции createCounter, но при этом она доступна в анонимной функции (это мы видели в предыдущих примерах). В строчке var counter = createCounter() анонимная функция запишется в переменную counter. Получится, что у нас далее есть функция counter, внутри которой доступна переменная num из createCounter.
Давайте убедимся в этом:
function createCounter() {
var num = 0; return function() {
num++;
return num;
};
}
var counter = createCounter();
alert(counter()); //выведет 1
Давайте теперь запустим функцию counter несколько раз - мы увидим, что каждый раз она будет выводить на единицу больше:
function createCounter() {
var num = 0;
return function() {
num++;
return num;
};
};
var counter = createCounter();
alert(counter()); //выведет 1
alert(counter()); //выведет 2
alert(counter()); //выведет 3
Еще раз: преимуществом такого подхода является то, что переменная num не видна снаружи createCounter и ее никто не сможет случайно затереть. Снаружи она не видна, но доступ к ней есть - через функцию counter, но только через нее.
Такая штука называется замыкание. Замыкание - это функция со всеми доступными внешними переменными (типа num в нашем случае). Эти переменные называются лексическим окружением функции.
Давайте вернемся назад к счетчику - осталось самое интересное: если createCounter вызвать несколько раз, записав результат в разные переменные - каждая из них станет независимым счетчиком.
В следующем примере у нас есть 2 функции: counter1 и counter2 - и они работают совершенно не мешая друг другу (получается, что у каждой из них своя переменная num):
function createCounter() {
var num = 0;
return function() {
num++;
return num;
};
};
var counter1 = createCounter();
alert(counter1()); //выведет 1
alert(counter1()); //выведет 2
alert(counter1()); //выведет 3
var counter2 = createCounter();
alert(counter2()); //выведет 1
alert(counter2()); //выведет 2
alert(counter2()); //выведет 3
Вызов функции на месте
При программировании иногда возникает задача сделать анонимную функцию и сразу ее вызвать в момент создания.
Зачем это нужно: те переменные и вспомогательные функции, которые мы объявим внутри нашей анонимной функции останутся локальными и не будут засорять своими именами глобальную область видимости.
Для вызова функции на месте хотелось бы сделать что-то в таком роде:
function() {
alert('!');
}();
Однако, это не будет работать, так как на месте разрешен вызов только функциональных выражений. Поэтому необходимо сделать из нашей функции выражение, например, поставив перед ней плюс, такое уже будет работать:
+function() {
alert('!');
}();
Так тоже будет работать:
!function() {
alert('!');
}();
Но более принято брать такие функции в круглые скобки, вот так:
(function() {
alert('!');
}());
Давайте посмотрим, что же нам дает вызов функции на месте: сделаем внутри такой функции переменную message. Наша переменная будет доступна только внутри функции, и не будет доступна снаружи:
(function() {
var message = '!';
alert(message);
}());
alert(message); //здесь message недоступна
Иногда во избежания ошибок в начале ставится точка с запятой (ошибки могут возникнуть при сжатии файла минимизатором):
;(function() {
alert('!');
}());
Создание модулей
https://learn.javascript.ru/closures-module
файл module.js:
var message = 'сообщение модуля';
function showMessage() {
alert(message);
}
Наш код
<script>
var message = 'наше сообщение';
</script>
<script src="module.js"></script>
<script>
alert(message); //выведет 'сообщение модуля', а не 'наше сообщение'
</script>
Допилим код модуля
(function() {
var message = 'сообщение модуля';
function showMessage() {
alert(message);
}
}());
вот теперь проблем не будет
экспорт функций наружу
(function() {
var message = 'сообщение модуля';
function showMessage() {
alert(message);
}
window.showMessage = showMessage;}());
экспорт как в библиотеке
(function() {
var message = 'сообщение модуля';
var $ = { showMessage: function (message) {
alert(message);
}
}
window.$ = showMessage;}());
Замыкания и вызов на месте
Замыкания и вызов функции на месте можно комбинировать. В следующем примере внешняя анонимная функция выполнится на месте и вернет внутреннюю анонимную функцию - она запишется в переменную func. А переменная counter попадет в замыкание:
var func = (function() {
var counter = 0;
//Эта функция запишется в func:
return function() {
counter++;
return counter;
}
})();
Применение замыканий
Пусть у нас дано несколько кнопочек. Давайте сделаем так, чтобы каждая кнопочка при нажатии увеличивала свое значение на единицу. Реализуем задачу на замыкании:
<button>0</button><button>0</button><button>0</button>
var buttons = document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = (function() {
var counter = 0;
//Эта функция привяжется к onclick:
return function() {
counter++; //берется из замыкания - для каждой кнопки своя переменная
this.innerHTML = counter;
}
})();
}
Результат выполнения кода (понажимайте на кнопочки):