При использовании асинхронных функций в классах часто возникает проблема, когда в теле функции невозможно обратиться к объекту класса, вызывающему функцию. Это хорошо видно на примере с аяксом (с использованием jQuery):
function Loader()
{
this.load = function()
{
$.ajax({
url: '/test.php',
success: function(data, textStatus, jqXHR) {
// здесь уже никак нельзя обратиться к объекту класса
console.log(this); // this содержит внутренний объект jQuery,
// вызывающий функцию success
}
});
}
}
(new Loader()).load();
Для решения этой проблемы в яваскрипте есть функция apply, которая позволяет вызвать любую функцию в контексте нашего объекта — т.е. this будет таким, каким мы скажем. Осталось только сделать callback-функцию, которая будет генирировать асинхронную функцию:
function cb(object, fnc)
{
return function() {
return fnc.apply(object, arguments);
}
}
object — объект, который будет подставляться в this
fnc — выполняемая функция
arguments — стандартный объект аргументов функции
Теперь результат будет таким:
function Loader()
{
this.load = function()
{
$.ajax({
url: '/test.php',
success: cb(this, this.onLoad)
})
}
this.onLoad = function(data, textStatus, jqXHR)
{
console.log(this); // this теперь содержит объект класса Loader
}
}
(new Loader()).load();
В качестве функции не обязательно передавать функцию объекта, можно создать функцию «на лету»:
cb(this, function(data, textStatus, jqXHR) {
console.log(this);
});
Сохранение исходного объекта this
В предыдущем примере мы потеряли объект, вызывающий функцию success (внутренний объект jQuery), но часто возникает потребность использовать вызывающий объект. Например, в событиях HTML-элементов:
function Button()
{
this.render = function()
{
var submit = $('<button>').html('click me');
submit.bind('click', function(event) {
// здесь this содержит HTML-элемент, вызывающий событие
console.log(this);
});
submit.appendTo($('body'));
}
}
(new Button()).render();
В данном примере вызывается событие click HTML-элемента button. Для того чтобы сохранить объект вызывающий функцию (в данном случае HTML-элемент button), callback-функция примет следующий вид:
function cb(object, fnc)
{
return function() {
var args = [this];
for (var i in arguments) args.push(arguments[i]);
return fnc.apply(object, args);
}
}
Таким образом, первым аргументом функции передается вызывающий объект, а остальные аргументы передаются в том же порядке. Результат будет следующим:
function Button()
{
this.render = function()
{
var submit = $('<button>').html('click me');
submit.bind('click', cb(this, this.onClick));
submit.appendTo($('body'));
}
this.onClick = function(target, event)
{
console.log(this); // this теперь содержит объект класса Button
console.log(target); // HTML-элемент, вызывающий событие
}
}
(new Button()).render();
Автор: BoneFletcher