вторник, 28 августа 2007 г.

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

На одном из форумов поднята следующая тема:
при использовании объекта XMLHttpRequest нет доступа
к свойствам текущего объекта.
Метод loadXml создаёт объект xmlDoc, который применяется для
получения данных с сервера в технике AJAX.

У xmlDoc есть свойство "onreadystatechange", которое должно быть
функцией. И является блоком кода, который вызывается объектом xmlDoc
при изменении статуса запроса к серверу.

Вопрос в том - Как xmlDoc вызывает эту функцию, как метод текущего объекта xmlTree
или по другому? Судя по тому, что в xmlTree не сохраняется свойство "this.a",
то "this" это не объект xmlTree.
Как выяснилось, в разных браузерах(в связи с разной реализацией объекта xmlHttpReqiest)
запуск кода "onreadystatechange" происходит в разном контексте.

В Mozilla код запускатеся в своём собственном объекте, не связанном
ни с чем. Проверяем дописывая в метод "process":

var s=""; for (i in this) s+=i+"="+this[i]+"\n"
alert(s)


В IE 6 запускается в контексте объекта window:

alert(this == window)

А в IE7 и Opera запускатся как метод объекта xmlHttpReqiest(у нас xmlDoc),
и написав:

alert(this.onreadystatechange)

в "process" можно увидеть код самого же вызвавшего alert метода.

Итого, код запускатся в чём угодно, но не в контексте xmlTree.

Для решения этой проблемы советуют использовать замыкание(closure)
слудующим образом:

function xmlTree()
{
function loadXml(url, parameters)
{
xmlDoc = new ActiveXObject("Microsoft.XMLHTTP")
var x = this
xmlDoc.onreadystatechange = function(){ x.process() }
xmlDoc.open( "GET", url+"?"+parameters, true)
xmlDoc.send( null )
}

function process()
{
if (xmlDoc.readyState!=4) { return }
this.a = 10
}

this.a=0
this.loadXml=loadXml
this.process=process
}


Т.е. переменной "х" присваивают текущий объект,
а свойству "onreadystatechange" присваивают анонимную(без названия) функцию
в которой происходит вызов "process" как метода "x": x.process().
И это действительно помогает!

Что происходит? Почему так работает и теперь "myTree.a" равно "10"?

Попробуем разобрать поподробнее:
1) При выполнении x = this происходит не присваивание x текущего объекта this,
а создаётся ссылка(в JS всё ссылка), то есть "x" не клон объекта-экземпляра
xmlTree, а ссылка на него.

2) Когда xmlDoc вызывает анонимную функцию, то внутри нее присходит
вызов метода process объекта "x". При этом изнутри анонимной функции
"видны" все переменные внешнего контекста, поэтому и возможен вызов "x.process".

3) Во время отработки "process" как метода "х" "this" является ссылкой
на объект "х", который в свою очередь является ссылкой
на объект "xmlTree". Т.е. все изменения сделанные в "х" отражаются
и в "xmlTree".

Рисунок 1