Опубликован: 19.10.2006 | Уровень: для всех | Доступ: свободно
Лекция 9:

Наследование и замыкание

< Лекция 8 || Лекция 9: 12 || Лекция 10 >

Замыкание

Замыкание ( сlosure ) является одним из наиболее мощных средств JavaScript. Если воспользоваться простым объяснением, то замыкание связывает внутренние и внешние переменные в функции. Почему это так важно? Потому что замыкание позволяет эмулировать почти любое свойство любого языка программирования, даже если оно не существует в JavaScript. Это звучит немного непонятно, поэтому лучше начать с более простого примера:

function beginAdding(a){
  a *= 5;
  return function finishAdding(b){ alert(a+b); }
}

var add = beginAdding(10);

add(20); // 70

Можно видеть, что в приведенном коде переменной 'a' присваивается значение 10 и передается в функцию beginAdding, в то время как переменной 'b' присваивается значение 20 и передается в функцию finishAdding.

А что содержится в переменной 'add'? Она содержит функцию finishAdding с копией связанной с ней всей функции beginAdding. Копия переменной 'a' из beginAdding сохраняется в памяти для дальнейшего использования.

Теперь, имея представление о замыкании, прежде чем продолжать, необходимо обсудить проблему утечки памяти. Internet Explorer, в частности, подвержен достаточно неприятным утечкам памяти при использовании замыкания. Чтобы понять, почему необходимо знать о сборке мусора в Internet Explorer, посмотрим, как в этом браузере выполняется очистка памяти от ненужных объектов.

Internet Explorer имеет два отдельных сборщика мусора: один для JavaScript и другой для объектов DOM. При выгрузке страницы браузер просматривает и удаляет весь код JavaScript и все объекты DOM со страницы. Утечка происходит, когда имеются циклические ссылки из объекта DOM в JavaScript и снова на объект DOM или из JavaScript-->Dom-->Javascript. Internet Explorer запутывается и не удаляет объекты при циклической ссылке.

var someInput    = document.getElementById('inputbox');
var someFunction = function(){
  alert(someInput.value);
}

someInput.onclick = someFunction;

Здесь представлен бесконечный цикл в терминах замыканий. Объект DOM someInput вовлечен в замыкание с функцией someFunction и наоборот. Браузер не может определить, что удалить в первую очередь, поэтому в результате имеем утечку памяти.

Замыкания создают, часто даже не осознавая этого. Возьмем, например, простую функцию:

function Animal(name){
  this.sleep = function(){ alert(name+' спит: Хрррр'); }
}

Можно не заметить этого, но переменная 'name' в функции sleep приходит из родительской функции Animal. Это создает замыкание.

Даже определение простейшей функции может создавать замыкание:

var x = 5;
var n = function(){
  x=10;
  return x;
}

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

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

var x       = 5;
var alertX1 = function(){ alert(x); }

x           = 10;
var alertX2 = function(){ alert(x); }

alertX1();
alertX2();

Обе функции в этом случае выведут 10. Почему? Потому что они обе указывают на одну и ту же копию х.


Если изменить x в любой точке, то обе функции отразят это. Как это исправить? Проще всего изменить область действия замыкания:

function makeClosure(x){
  return function(){ alert(x); }
}

var x       = 5;
var alertX1 = makeClosure(x);

x           = 10;
var alertX2 = makeClosure(x);

alertX1(); // 5
alertX2(); // 10

Это решает проблему. Если, однако, этот код создавал утечку памяти, то утечка будет существенно больше, чем в предыдущем примере; и также используется больший объем памяти. Оказывается, что в действительности имеется три различных области действия в этом простом примере:


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

function makeClosure(x){
  return function(){ alert(x.val); }
}

var x       = {val:5};
var alertX1 = makeClosure(x);

x.val       = 10;
var alertX2 = makeClosure(x);

alertX1(); // 10
alertX2(); // 10

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

Как избежать утечки памяти при использовании замыканий? Необходимо избегать использования циклических ссылок. Наиболее распространенной причиной утечки памяти является присоединение событий, таких, как событие onclick, к объектам DOM.

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

"Основы приложений AJAX" будет посвящена основам приложений AJAX!

< Лекция 8 || Лекция 9: 12 || Лекция 10 >
Елена Сапегова
Елена Сапегова

После прохождения теоретической части пришло письмо об окончании теоретической части курса, будет ли практическая часть?

Рустам Рахимов
Рустам Рахимов

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

function Complete() {

    var x = "Имя: " + document.tutform.firstname.value;

alert(x);

} - Так брать значение из формы tutform, firstname-получаем значение из имени. 

<INPUT TYPE="button" VALUE="Готово" onClick="Complete();"> - Нужно к кнопке прописать событие, при нажатие на кнопку готово, чтобы возвращалось значение с помощью функции. А так ничего не работает, и смысла учить нету, если не знать почему не работает.