08. Функции. Часть 2. Замыкания
Зачем нам замыкания?
|
В наших планах:
То есть наша конечная цель — практика гипертекстового конструирования с использованием библиотеки jQuery. |
Замыкания — один из основных приемов использования средств библиотеки jQuery. Поэтому наш интерес к замыканиям носит не абстрактный, а прикладной характер.
Ниже приводится пример, который не использует библиотеку jQuery, но работает в её духе. Чтобы было понятно уже сейчас, как примерно это устроено.
praxis/08/examples/01/index.htm
Код примера:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<HTML lang="ru">
<HEAD>
<META http-equiv="content-type" content="text/html; charset=windows-1251">
<TITLE>Отсчет времени</TITLE>
<SCRIPT>
window.onload = function()
{
var time = 10;
var n = window.setInterval(function ()
{
if (!time--) {clearInterval(n); time="Время закончилось"; }
document.getElementById("counter").innerHTML = time;
},1000);
};
</SCRIPT>
</HEAD>
<BODY>
<H1>Отсчет времени</H1>
<DIV id=counter>Подождите секунду!</DIV>
</BODY>
</HTML>
Через секунду после запуска примера начнется отсчет времени, и по истечении 10 секунд на экране появится надпись: «Время закончилось».
Разберемся, как это работает.
Код внутри BODY состоит из двух элементов: статичного заголовка H1 и блока DIV с идентификатором counter. Именно в этот блок и будут выводиться сообщения при работе скрипта.
Посмотрим, как работает скрипт.
01 window.onload = function()
02 {
03 var time = 10;
04 var n = window.setInterval(function ()
05 {
06 if (!time--) {clearInterval(n); time="Время закончилось"; }
07 document.getElementById("counter").innerHTML = time;
08 },1000);
09 };
В строке 01 в качестве обработчика события onload («окончание загрузки документа») назначается функциональный литерал. Он будет запущен на выполнение один раз, после того как наступит событие «текущий HTML-документ полностью загружен в браузер» (построены объекты документа, соответствующие его элементам).
Теперь посмотрим, что выполнится внутри этого литерала. В строке 03 создана локальная переменная time, и ей присвоено значение 10. Сразу отметим, эта локальная переменная попадает в замыкание — объект вызова литерала не будет уничтожен сборщиком мусора. Почему, увидим далее.
В строке 04 создана еще одна локальная переменная n, соответственно, она тоже окажется в замыкании. Переменной n присваивается номер таймера, который создается методом setInterval (метод объекта window).
Напомню, как работает метод setInterval в паре с методом clearInterval.
Форматы этих методов:
номер_таймера = setInterval(функция, число_миллисекунд);
clearInterval(номер_таймера);
Метод setInterval создает таймер и возвращает его номер. Таймер работает так: через каждые число_миллисекунд он будет вызывать функцию, заданную в методе setInterval первым аргументом.
Функции, которые задаются в качестве аргументов других функций, называют функциями обратного вызова.
Метод clearInterval(номер_таймера) уничтожает таймер с номером, заданным в качестве аргумента этого метода.
Идём далее.
01 window.onload = function()
02 {
03 var time = 10;
04 var n = window.setInterval(function ()
05 {
06 if (!time--) {clearInterval(n); time="Время закончилось"; }
07 document.getElementById("counter").innerHTML = time;
08 },1000);
09 };
Итак, таймер создается в строках 04—08. Второй аргумент число_миллисекунд равен 1000, то есть одной секунде. В качестве первого аргумента метода setInterval задан функциональный литерал (который таймер будет вызывать каждую секунду).
Посмотрим, что записано в этом втором (вложенном в первый) функциональном литерале.
В строке 06 проверяется и уменьшается на 1 переменная time. Если эта переменная стала нулем, вызывается метод clearInterval, он уничтожает таймер, а переменной time присваивается строка "Время закончилось".
В строке 07 содержимое переменной time записывается внутрь HTML-элемента с идентификатором counter (то есть в наш блок <DIV id=counter>...</DIV>). Отметим, что:
- document — ссылка на объект document
- document.getElementById("counter") — ссылка на элемент c id=counter.
- document.getElementById("counter").innerHTML — ссылка на свойство innerHTML элемента counter
Свойство innerHTML, доступно для чтения и записи. Оно содержит HTML-код, записанный внутри элемента.
Сведем все воедино. При наступлении события «документ загружен» будет выполнен (один раз) функциональный литерал, начинающийся в строке 01. Этот литерал создаст таймер, в котором в качестве функции обратного вызова задаётся литерал, ссылающийся на локальные переменные внешнего литерала (на переменные time и n). Значит, после завершения работы внешнего литерала его объект вызова не будет уничтожен сборщиком мусора. Получается замыкание.
Итак, код 01—09 отработал. В нём был создан таймер, который обращается к функции, заданной внутри внешнего литерала. Образовалось замыкание, и переменные time и n будут доступны функции обратного вызова, несмотря на то, что выполнение кода 01—09 уже закончилось.
Чем хорош этот код? Тем, что он не использует ни одной (заметьте ни одной!) глобальной переменной (onload не считается, это свойство объекта window является встроенным).
Кроме того, код хорош тем, что он полностью отделён от HTML-разметки (в ней нет даже атрибута onload для создания обработчика события).
Мы неоднократно говорим о том, что содержание и представление должны «спать в разных кроватях». Вот и третья «кровать» — для поведения. Скоро мы сможет обоснованно написать на своем флаге мудрый принцип современного web-конструирования:
- HTML-файл — для содержания
- CSS-файл — для представления
- JavaScript-файл — для поведения
Этот принцип, плюс избавление от глобальных переменных при помощи замыканий, присущи библиотеке jQuery с которой мы познакомимся в заметке 15.