Программная реализация обратных вызовов с помощью JS / jQuery - PullRequest
10 голосов
/ 30 сентября 2011

Итак, я пишу веб-приложение. Почти все сделано на стороне клиента, сервер - всего лишь интерфейс RESTful. Я использую jQuery в качестве своего предпочтительного фреймворка и внедряю свой код в Шаблон модуля раскрытия .

Каркас моего кода в основном выглядит следующим образом:

(function($){
    $.fn.myplugin = function(method)
    {
        if (mp[method])
        {
            return mp[method].apply(this, Array.prototype.slice.call(arguments, 1));
        }
        else if (typeof method === 'object' || ! method)
        {
            return mp.init.apply(this, arguments);
        }
        else
        {
            $.error('Method ' +  method + ' does not exist on $.myplugin');
        }
    };

    var mp =
    {
        init : function( options )
        {
            return this.each(function()
            {
                // stuff
            }
        },
        callbacks : {},
        addCallback : function(hook_name, cb_func, priority)
        {
            // some sanity checking, then push cb_func onto a stack in mp.callbacks[hook_name]
        },
        doCallbacks : function(hook_name)
        {
            if (!hook_name) { hook_name = arguments.callee.caller.name; }
            // check if any callbacks have been registered for hook_name, if so, execute one after the other
        }
    };
})(jQuery);

Довольно просто, верно?

Теперь мы можем регистрировать (множественные, иерархические) обратные вызовы как внутри, так и вне области приложения.

Что меня беспокоит: чтобы сделать все это максимально расширяемым, мне придется прибегнуть к чему-то следующему:

foo : function() {
    mp.doCallbacks('foo_before');
    // do actual stuff, maybe some hookpoints in between
    mp.doCallbacks('foo_after');        
}

Каждая функция внутри моего приложения должна начинаться и заканчиваться следующим образом. Это просто кажется неправильным.

Итак, волшебники JS из SO - что делать?

Ответы [ 4 ]

10 голосов
/ 30 сентября 2011

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

function withCallbacks(name, func)
{
    return function() {
        mp.doCallbacks(name + "_before");
        func();
        mp.doCallbacks(name + "_after"); 
    };
}

Тогда вы можете написать что-то вроде:

foo: withCallbacks("foo", function() {
    // Do actual stuff, maybe some hookpoints in between.
})
4 голосов
/ 04 октября 2011

Возможно, я не правильно понял вопрос, потому что не понимаю, почему вы не добавляете код для вызова обратных вызовов непосредственно в коде myplugin:

$.fn.myplugin = function(method)
{
    if (mp[method])
    {
        var params = Array.prototype.slice.call(arguments, 1), ret;
        // you might want the callbacks to receive all the parameters
        mp['doCallbacks'].apply(this, method + '_before', params);
        ret = mp[method].apply(this, params);
        mp['doCallbacks'].apply(this, method + '_after', params);
        return ret;
    }
    // ...
}

РЕДАКТИРОВАТЬ:

Хорошо, после прочтения вашего комментария я думаю, что другим решением будет (конечно) другое косвенное.То есть иметь функцию invoke, которая используется из конструктора, а также другие открытые методы для вызовов между собой.Я хотел бы отметить, что он будет работать только для открытых методов, так как присоединение к хукам приватных методов в любом случае нарушает инкапсуляцию.

Простая версия будет выглядеть примерно так:

function invoke(method) {
    var params = Array.prototype.slice.call(arguments, 1), ret;
    // you might want the callbacks to receive all the parameters
    mp['doCallbacks'].apply(this, method + '_before', params);
    ret = mp[method].apply(this, params);
    mp['doCallbacks'].apply(this, method + '_after', params);
}

$.fn.myplugin = function() {
   // ...
   invoke('init');
   // ...
};

Но на самом деле я написал немного больше кода, который также уменьшил бы дублирование между плагинами.Вот как будет выглядеть создание плагина в конце

(function() {

function getResource() {
    return {lang: "JS"};
}

var mp = NS.plugin.interface({
    foo: function() {
        getResource(); // calls "private" method
    },
    bar: function() {
        this.invoke('foo'); // calls "fellow" method
    },
    init: function() {
        // construct
    }
});

$.fn.myplugin = NS.plugin.create(mp);

})();

А вот так выглядит частичная реализация:

NS = {};
NS.plugin = {};

NS.plugin.create = function(ctx) {
    return function(method) {
        if (typeof method == "string") {
            arguments = Array.prototype.slice.call(arguments, 1);
        } else {
            method = 'init'; // also gives hooks for init
        }

        return ctx.invoke.apply(ctx, method, arguments);
    };
};

// interface is a reserved keyword in strict, but it's descriptive for the use case
NS.plugin.interface = function(o) {
    return merge({
        invoke:      NS.plugin.invoke,
        callbacks:   {},
        addCallback: function(hook_name, fn, priority) {},
        doCallbacks: function() {}
    }, o);
};

NS.plugin.invoke = function(method_name) {
    if (method_name == 'invoke') {
        return;
    }

    // bonus (if this helps you somehow)
    if (! this[method]) {
        if (! this['method_missing') {
            throw "Method " + method + " does not exist.";
        } else {
            method = 'method_missing';
        }
    }

    arguments = Array.prototype.slice.call(arguments, 1);

    if (method_name in ["addCallbacks", "doCallbacks"]) {
        return this[method_name].apply(this, arguments);
    }

    this.doCallbacks.apply(this, method_name + '_before', arguments);
    var ret = this[method_name].apply(this, arguments);
    this.doCallbacks.apply(this, method_name + '_after', arguments);

    return ret;
};

Конечно, это полностью не проверено:)

2 голосов
/ 30 сентября 2011

вы по сути реализуете урезанную версию jQueryUI Widget factory .я бы порекомендовал использовать эту функцию, чтобы избежать необходимости делать это самостоятельно.

фабрика виджетов автоматически сопоставит строки с вызовами методов, так что:

$("#foo").myPlugin("myMethod", "someParam")

вызовет myMethod на экземпляре плагина с 'someParam' в качестве аргумента.Кроме того, если вы запускаете пользовательское событие, пользователи могут добавлять обратные вызовы, добавляя свойство к параметрам, которое соответствует имени события.

Например, виджет tabs имеет событие selectк которому вы можете подключиться, добавив свойство select к опциям во время инициализации:

$("#container").tabs({
    select: function() {
        // This gets called when the `select` event fires
    }
});

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

надеюсь, что это поможет.ура!

1 голос
/ 09 октября 2011

В основном я предпочитаю избегать обратных вызовов и использовать события вместо этого. Причина проста. Я могу добавить более одной функции для прослушивания данного события, мне не нужно связываться с параметрами обратного вызова и не нужно проверять, определен ли обратный вызов. Поскольку все ваши методы вызываются через $.fn.myplugin, легко вызывать события до и после вызова метода.

Вот пример кода:

(function($){
    $.fn.myplugin = function(method)
    {
        if (mp[method])
        {
            $(this).trigger("before_"+method);
            var res = mp[method].apply(this, Array.prototype.slice.call(arguments, 1));
            $(this).trigger("after_"+method);
            return res;
        }
        else if (typeof method === 'object' || ! method)
        {
            return mp.init.apply(this, arguments);
        }
        else
        {
            $.error('Method ' +  method + ' does not exist on $.myplugin');
        }
    };

    var mp =
    {
        init : function( options )
        {
            $(this).bind(options.bind);
            return this.each(function()
            {
                // stuff
            });
        },

        foo: function() {
            console.log("foo called");
        }
    };
})(jQuery);


$("#foo").myplugin({
    bind: {
        before_foo: function() {
            console.log("before foo");
        },

        after_foo: function() {
            console.log("after foo");
        }
    }
});
$("#foo").myplugin("foo");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...