При использовании асинхронных функций в классах часто возникает проблема, когда в теле функции невозможно обратиться к объекту класса, вызывающему функцию. Это хорошо видно на примере с аяксом (с использованием jQuery):
Для решения этой проблемы в яваскрипте есть функция apply, которая позволяет вызвать любую функцию в контексте нашего объекта — т.е. this будет таким, каким мы скажем. Осталось только сделать callback-функцию, которая будет генирировать асинхронную функцию:
object — объект, который будет подставляться в this
fnc — выполняемая функция
arguments — стандартный объект аргументов функции
Теперь результат будет таким:
В качестве функции не обязательно передавать функцию объекта, можно создать функцию «на лету»:
В предыдущем примере мы потеряли объект, вызывающий функцию success (внутренний объект jQuery), но часто возникает потребность использовать вызывающий объект. Например, в событиях HTML-элементов:
В данном примере вызывается событие click HTML-элемента button. Для того чтобы сохранить объект вызывающий функцию (в данном случае HTML-элемент button), callback-функция примет следующий вид:
Таким образом, первы�� аргументом функции передается вызывающий объект, а остальные аргументы передаются в том же порядке. Результат будет следующим:
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();