Шаблон проектирования плагинов 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");
});
Я опубликовал свой вопрос в других местах, где также были собраны некоторые интересные ответы:
4 ответа
Сначала одно: если вы хотите назвать что-то вроде this.fill('red');
где это экземпляр jQuery, вы должны расширить прототип jQuery и сделать fill()
"Общественность". JQuery предоставляет рекомендации по расширению своего прототипа с помощью так называемых "плагинов", которые можно добавить с помощью $.fn.fill
, который так же, как jQuery.prototype.fill
,
В обратных вызовах jQuery это часто является ссылкой на элемент 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, если вы не прикрепите их где-нибудь. Вы можете добавить столько методов, сколько вам нужно, в объект Private, и пока вы возвращаете это, вы также можете связывать их в стиле jQuery, как я делал в примере.
Как насчет (в рамках плагина):
var fill = function ()
{
(function (color)
{
this.css ('backgrorund-color', color);
//.. your stuff here ...
}).apply (this, arguments);
}
$.fn.myplugin = function ()
{
fill ('green');
}
Таким образом, fill сохранит контекст jQuery, в котором вы находитесь, и по-прежнему будет закрыт для вашего плагина.
Изменено: вышеупомянутое неверно по отношению к области видимости, попробуйте следующее:
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');
}
Возможно, вы захотите взглянуть на то, как реализована 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);
К сожалению, "частные" методы (или любое другое свойство) никогда не могут быть вызваны с префиксом "this" в javascript. Все, что называется как this.myFunc(myArgs)
должен быть общедоступным.
А "частные" методы могут быть вызваны только из той области, в которой они были определены.
Ваше оригинальное решение - единственное, которое будет работать. Да, это боль, которую нужно пройти в this
, но нет более многословия, чем было бы, если бы ваш невозможный запрос был возможен:
this.fill('green');
//or
fill(this,'green');
Как вы можете видеть, они оба занимают одинаковое количество символов в вашем коде.
Извините, но вы застряли с этим в качестве решения, если только вы не хотите создать новое пространство имен и сделать их не приватными - что просто добавит к объему кода, который вам нужно написать, то есть к тому, что вы косвенно назвали "не подвергается прямому воздействию":
this.myplugin.fill('green');
... является более многословным, таким образом, вид побеждает цель.
Javascript не похож на другие языки, здесь нет "закрытых" членов как таковых, только члены, доступные в замыканиях, которые иногда могут использоваться аналогичным образом для закрытых членов, но это скорее "обходной путь", а не " реальная сделка "частных пользователей, которых вы ищете.
Может быть трудно с этим смириться (я часто борюсь), но не пытайтесь втиснуть javascript в то, что вы понимаете из других языков, принимайте это за то, что есть...