Объект callbacks

Начиная с версии 1.7, в jQuery появились объекты callbaks. Они позволяют организовывать наборы функций обратного вызова. Именно с помощью callbaks в jQuery организованы ajax и объекты deferred. Конечно, callbaks'ы могут быть полезны и для создания собственных удобных компонентов. (Более подробное описание использования объектов callbaks находится после списка методов).

Содержание

Список методов

.add() добавляет функцию в объект callbacks.
.disable() «отключает» объект callbacks, в результате чего тот теряет возможность выполняться.
.empty() удаляет все функций из текущего объекта callbacks.
.fire() выполняет все функции объекта callbacks (если тот не отключен).
.fireWith() выполняет все функции объекта callbacks с заданным контекстом.
.fired() проверяет, выполнялся ли текущий объект callbacks хотя бы один раз.
.has() проверяет наличие заданной функции в объекте callbacks.
.remove() удаляет заданную функцию из объекта callbacks.

Создание

Для создания экземпляра объекта Callbacks можно воспользоваться функцией

$.Callbacks([flags])

она возвращает новый callbacks-объект. В качестве аргумента можно передать строку с опциями (флагами) callbacks-объекта (если опций несколько, то нужно указывать их через пробел). Список возможных опций можно найти ниже.

// создадим новенький, нетронутый объект Callbacks и поместим
// его в переменную newCallbacks.
var newCallbacks = $.Callbacks();

После создания, в объект callbacks можно будет добавлять функции, и затем вызывать все добавленные функции разом.

Использование

Допустим, заданы две функции fn1 и fn2, которые умеют выводить текст в некую консоль:

function fn1( value ){
    console.log( value );
}
 
function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}

добавим их в объект callbacks (предварительно его создав):

var callbacks = $.Callbacks();
// добавим в callbacks функцию fn1
callbacks.add( fn1 );
// вызовем все функции callbacks (пока что одну - fn1)
// в итоге, в консоль будет выведено: foo!  
callbacks.fire( "foo!" );
 
// добавим в callbacks вторую функцию - fn2
// (теперь в нем будет уже две функции)
callbacks.add( fn2 );
// и они обе будут выполнены при вызове fire:
callbacks.fire( "bar!" );
 
// в итоге, в консоль будет добавлено: "bar!" и "fn2 says: bar!"
 
// удалим из нашего callbacks функцию fn2
callbacks.remove(fn2);
callbacks.fire( "foobar" );
 
// в итоге, в callbacks останется только fn1 и при вызове fire
// будут выполнена только она - в консоли появится "foobar"

Опции (параметр flag)

При создании объекта callbacks можно задать для него опции (настройки, флаги). Это делается в необязательном строковом параметре flag (чтобы указать сразу несколько настроек, нужно перечислить их через пробел) функции $.Callbacks(). Возможные настройки:

once — выполнение объекта callbacks будет возможно только один раз (подобно объектам deferred). Если эта опция не указана, то вызывать набор функций объекта callbacks можно будет любое количество раз.
memory — объект callbacks будет запоминать все совершенные вызовы. При добавлении в callbacks новых функций, они будут тотчас выполняться в контексте запомненных ранее вызовов (подобно deferred).
unique — одну и ту же функцию нельзя будет включать в один callbacks более одного раза.
stopOnFalse — если один из функций-участников callbacks вернет false, то оставшиеся функции вызваны не будут.

Примеры использования опций

В примерах будут использованы функции fn1 и fn2 определенные выше.

Пример $.Callbacks('once'):

var callbacks = $.Callbacks( "once" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );
 
/*
В консоли появится только:
"foo"
*/

Пример $.Callbacks('memory'):

var callbacks = $.Callbacks( "memory" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );
 
/*
В консоли появится:
foo
fn2 says:foo
bar
fn2 says:bar
foobar
*/

Пример $.Callbacks('unique'):

var callbacks = $.Callbacks( "unique" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn1 ); // repeat addition
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );
 
/*
В консоли появится:
foo
bar
fn2 says:bar
foobar
*/

Пример $.Callbacks('stopOnFalse'):

function fn1( value ){
    console.log( value );
    return false;
}
 
function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}
 
var callbacks = $.Callbacks( "stopOnFalse");
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );
 
/*
В консоли появится:
foo
bar
foobar
*/

В $.Callbacks() можно указать сразу несколько опций. В этом случае, их возможности будут «пересекаться» — каждая из них будет возможна только если не противоречит остальным указанным опциям. Например, если указаны 'unique memory', то при добавлении уже существующей функции, предыдущие (запомненные) вызовы объекта callbacks не будут осуществлены, поскольку функция является дублем и не может быть добавлена.

$.Callbacks('unique memory'):

function fn1( value ){
    console.log( value );
    return false;
}
 
function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}
 
var callbacks = $.Callbacks( "unique memory" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn1 ); // repeat addition
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.add( fn2 );
callbacks.fire( "baz" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );
 
/*
В консоли появится:
foo
fn2 says:foo
bar
fn2 says:bar
baz
fn2 says:baz
foobar
*/

Комбинация опций 'memory once' используется jQuery при организации методов .done() и .fail() объекта deferred


Методы отдельного объекта callbacks можно присвоить переменным и полям сторонних объектов, для более удобного использования:

/*
Создадим объект Callbacks и присвоим его методы
отдельным переменным
*/
var myCallbacks = $.Callbacks(),
    add = myCallbacks.add,
    remove = myCallbacks.remove,
    fire = myCallbacks.fire;
 
add( fn1 ); // вызовет метод add экземпляра myCallbacks
fire( "hello world");
remove( fn1 );

$.Callbacks, $.Deferred и концепция Pub/Sub

Объекты callbacks и deferred помогают легко реализовывать концепцию Publisher/Subscriber (паттерн Наблюдатель). Она незаменима, когда группа объектов должна знать состояние других объектов. Первые объекты называют подписчиками (наблюдателями), а вторые писателями (наблюдаемыми). Вместо того, чтобы постоянно проверять актуальное состояние писателей, подписчики могут просто подписаться на его изменение. А когда оно произойдет, писатели сами оповестят об этом всех своих подписчиков.


Продемонстрируем как с помощью callbacks можно организовать собственные компоненты. Для этого реализуем набор сообщений (например форума), с системой подписчиков.

var topics = {};
 
jQuery.Topic = function( id ) {
    var callbacks,
        method,
        topic = id && topics[ id ];
    if ( !topic ) {
        callbacks = jQuery.Callbacks();
        topic = {
            publish: callbacks.fire,
            subscribe: callbacks.add,
            unsubscribe: callbacks.remove
        };
        if ( id ) {
            topics[ id ] = topic;
        }
    }
    return topic;
};

Этот небольшой плагин можно будет использовать для подписки и оповещении о публикации новых сообщений, например так:

// "Подписка"
$.Topic( "mailArrived" ).subscribe( fn1 );
$.Topic( "mailArrived" ).subscribe( fn2 );
$.Topic( "mailSent" ).subscribe( fn1 );
 
// "Публикация"
$.Topic( "mailArrived" ).publish( "hello world!" );
$.Topic( "mailSent" ).publish( "ух ты! сообщение!" );
 
/*
В итоге, "hello world!" будет передан в fn1 and fn2,
а "ух ты! сообщение!" только в fn1.
И в результате будет выведено:
"hello world!"
"fn2 says: hello world!"
"ух ты! сообщение!"
*/

Как вы видите, получившийся плагин оказался удобным. Однако он имеет один недостаток — методы предназначенные для "писателей" будут доступны и для наблюдателей. Это таит в себе потенциальную опасность. По хорошему, эти функции должны быть доступны только внутри объектов, ответственных за публикацию сообщений. Этого можно добиться, если использовать для этих целей объекты deferred, которые могут предоставлять своих заместителей с ограниченной функциональностью.

Поисковые ключи:
  • callbacks