Шаблон разработки плагина jQuery (обычная практика?) для работы с частными функциями

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

Мои плагины обычно выглядят примерно так:

(function($) {

  $.fn.myplugin = function(...) {
    ...
    // some shared functionality, for example:
    this.css('background-color', 'green');
    ...
  };
  $.fn.mypluginAnotherPublicMethod = function(...) {
    ...
    // some shared functionality, for example:
    this.css('background-color', 'red');
    ...
  };

}(jQuery));

Теперь мой вопрос: как аккуратно СУШИТЬ эту общую функциональность? Очевидным решением было бы поместить его в функцию в пространстве имен плагина:

var fill = function($obj, color) {
  $obj.css('background-color', color);
};

Хотя это решение является эффективным и имеет красивое пространство имен, мне оно очень не нравится. По одной простой причине: я должен передать ему объект jQuery. Т.е. Я должен называть это так: fill(this, 'red');, а я хотел бы называть это так: this.fill('red');

Конечно, мы могли бы достичь этого результата, просто поместив fill в jQuery.fn. Но это очень неудобно. Представьте, что на основе этого подхода разработано десять плагинов, и каждый плагин помещает пять из этих «частных» функций в пространство имен функций jQuery. Это заканчивается большим беспорядком. Мы могли бы смягчить это, добавив к каждой из этих функций префикса имени плагина, которому они принадлежат, но это не делает ее более привлекательной. Эти функции должны быть частными для плагина, поэтому мы вообще не хотим открывать их внешнему миру (по крайней мере, не напрямую).

Итак, у меня есть вопрос: есть ли у кого-нибудь из вас предложения, как получить лучшее из обоих миров? То есть; код плагина, способный вызывать «частные» функции плагина аналогично this.fill('red') (или this.myplugin.fill('red'), или даже this.myplugin().fill('red') и т. д.), предотвращая при этом загрязнение пространства имен функций jQuery. И, конечно же, он должен быть легким, поскольку эти частные функции могут вызываться очень часто.


ОБНОВЛЕНИЕ: спасибо за ваши предложения.

Мне особенно нравится идея Дэвида определить тип объекта, который содержит «частные» функции и обертывает объект jQuery. Единственная проблема в том, что он по-прежнему не позволяет мне связывать «частные» и «общедоступные» функции. Это было серьезной причиной, по которой нужно начинать с синтаксиса типа this.fill('red').

В итоге я получил решение, которое считаю не слишком элегантным, но привлекательным для «лучшего из обоих миров», потому что:

$.fn.chain = function(func) {
    return func.apply(this, Array.prototype.slice.call(arguments, 1));
};

Это позволяет создавать такие конструкции, как:

this.
    find('.acertainclass').
    chain(fill, 'red').
    click(function() {
        alert("I'm red");
    });

Я разместил свой вопрос в других местах, где тоже собраны интересные ответы:


person Tim Molendijk    schedule 14.01.2010    source источник


Ответы (4)


Прежде всего: если вы хотите вызвать что-то вроде this.fill('red');, где this является экземпляром jQuery, вам необходимо расширить прототип jQuery и сделать fill() "общедоступным". jQuery предоставляет рекомендации по расширению своего прототипа с помощью так называемых «подключаемых модулей», которые могут быть добавлены с помощью $.fn.fill, что аналогично jQuery.prototype.fill.

В обратных вызовах jQuery this часто является ссылкой на элемент HTML, и вы не можете добавлять к ним прототипы (пока). Это одна из причин, по которой jQuery обертывает элементы и возвращает экземпляры jQuery, которые можно легко расширить.

Используя синтаксис (function(){})();, вы можете создавать и выполнять «частный» javascript на лету, и все это исчезает, когда это будет сделано. Используя эту технику, вы можете создать свой собственный синтаксис, подобный jQuery, который превращает jQuery в ваш собственный частный объект с цепочкой.

(function(){
    var P = function(elem) {
        return new Private(elem);
    };
    var Private = function(elem) {
        this.elem = jQuery(elem);
    }
    Private.prototype = {
        elem: null,
        fill: function(col) {
            this.elem.css('background',col);
            return this;
        },
        color: function(col) {
            this.elem.css('color', col);
            return this;
        }
    }

    $.fn.myplugin = function() {
        P(this).fill('red');
    };
    $.fn.myotherplugin = function() {
        P(this).fill('yellow').color('green');
    };

})();

$('.foo').myplugin();
$('.bar').myotherplugin();

console.log(typeof P === 'undefined') // should print 'true'

Таким образом, P обозначает ваш собственный набор «частных» функций. Они не будут доступны где-либо еще в коде или в пространстве имен jQuery, если вы не прикрепите их куда-нибудь. Вы можете добавить столько методов, сколько захотите, в частный объект, и пока вы возвращаете this, вы также можете связать их в стиле jQuery, как я сделал в примере.

person David Hellsing    schedule 14.01.2010

Как насчет (в рамках плагина):

var fill = function ()
{
    (function (color) 
    {
        this.css ('backgrorund-color', color);
        //.. your stuff here ...
    }).apply (this, arguments);
}

$.fn.myplugin = function ()
{
    fill ('green');
}

Таким образом, fill сохранит контекст jQuery, в котором вы находитесь, и останется приватным для вашего плагина.


Изменено: приведенное выше неверно относительно scoping, попробуйте вместо этого следующее:

var fill = function (color)
{
    if (!$this) return; // break if not within correct context

    $this.css ('backgrorund-color', color);
    //.. your stuff here ...
}

$.fn.myplugin = function ()
{
    var $this = $(this); // local ref to current context
    fill ('green');
}
person K Prime    schedule 14.01.2010
comment
Так как же fill () удерживает this из $ .fn.myplugin ()? - person Tim Molendijk; 14.01.2010
comment
this не будет ли (function(color) {...}) функцией? - person nickf; 14.01.2010
comment
Я думаю, что это this в этом примере кода: (1) $obj.myplugin(): $obj (2) fill(): window (3) function(color): window - person Tim Molendijk; 14.01.2010
comment
@nickf - this относится к вызывающему объекту, а не к функции. Например, если вы позвонили obj.fill (), this будет obj. Используя apply, вы можете установить this в текущий контекст во время вызова, т.е. объект jQuery - person K Prime; 14.01.2010
comment
@ Тим - ты совершенно прав, я неправильно рассчитал this переходы области видимости. Соответственно изменил свой ответ - person K Prime; 14.01.2010
comment
Это правильный ответ. Не могу поверить, что у меня был только один голос, когда я появился. Я предполагаю, что ошибки @KPrime в исходном ответе сбили людей с пути ... но с его последними исправлениями ответ прямо на деньги. Функция fill находится внутри области действия плагина и, следовательно, имеет к ней доступ. Другого способа вызвать fill нет, поэтому не нужно ставить чек (но это может помочь по мере развития кода в будущем). Он делает все, о чем задан вопрос, и никакой другой ответ не делает. - person Charlie Flowers; 18.10.2010

Вы можете посмотреть, как реализована jQuery UI Widget Factory.

Базовый подход такой:

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

    // private methods, internally accessible via this.foo, this.bar
    var foo = function() { … };
    var bar = function() { … };

    var private = { // alternative approach to private methods, accessible via this.private.foo
        foo : function() { … },
        bar : function() { … }
    }

    var mp = { // public methods, externally accessible via $.myplugin('foo', 'bar')
        init : function( options )
        {
            return this.each(function()
            {
                // do init stuff
            }
        },
        foo : function() { … },
        bar : function() { … }
    };
})(jQuery);
person vzwick    schedule 05.10.2011
comment
вы хотите использовать в качестве имени переменной отличное от private, поскольку это зарезервированное (для будущего использования) слово в ECMAScript - person jinglesthula; 18.01.2012
comment
@jinglesthula Хороший улов, спасибо! - person vzwick; 19.01.2012

К сожалению, «частные» методы (или любое свойство в этом отношении) нельзя никогда вызывать с префиксом this в javascript. Все, что называется this.myFunc(myArgs), должно быть общедоступным.

А «частные» методы можно вызывать только из той области, в которой они были определены.

Ваше оригинальное решение - единственное, что будет работать. Да, это боль, которую нужно передать this, но многословия не больше, чем было бы, если бы ваш невозможный запрос был возможен:

this.fill('green');
//or
fill(this,'green');

Как видите, оба они занимают одинаковое количество символов в вашем коде.

Извините, но вы застряли в этом как на решении, если только вы не хотите создать новое пространство имен и сделать его не закрытым, что просто добавит к количеству кода, который вам нужен напишите, то есть то, что вы косвенно назвали "не подвергается прямому воздействию":

this.myplugin.fill('green');

... более многословен, таким образом, как бы поражает цель.

Javascript не похож на другие языки, в нем нет "частных" членов как таковых, только члены, доступные в рамках замыканий, которые иногда могут использоваться аналогично закрытым членам, но это скорее "обходной путь", а не " настоящие "частные участники", которых вы ищете.

С этим может быть трудно смириться (я часто борюсь), но не пытайтесь превратить javascript в то, что вы понимаете из других языков, принимайте его таким, какой он есть ...

person Graza    schedule 14.01.2010