我一直在开发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函数命名空间污染。当然它应该是轻量级的,因为这些私有函数可能会被频繁调用。
更新:感谢您的建议。
我特别喜欢David的定义一个包含'private'函数并包装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");
});
我在其他地方交叉发布我的问题,这也收集了一些有趣的回复:
答案 0 :(得分:11)
首先要做的事情:如果您想调用类似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对象中添加任意数量的方法,只要您返回 this ,您也可以像我在示例中那样将它们链接为jQuery样式。
答案 1 :(得分:2)
如何(在插件的范围内):
var fill = function ()
{
(function (color)
{
this.css ('backgrorund-color', color);
//.. your stuff here ...
}).apply (this, arguments);
}
$.fn.myplugin = function ()
{
fill ('green');
}
这样,填充将保留您所在的jQuery上下文,并且仍然是您的插件的私有
修正:上述内容不正确w.r.t.范围界定,请尝试以下方法:
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');
}
答案 2 :(得分:2)
您可能想了解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);
答案 3 :(得分:1)
不幸的是,“私有”方法(或任何属性)可以永远在javascript中使用“this”前缀进行调用。任何被称为this.myFunc(myArgs)
的内容都必须公开。
“私人”方法只能在定义它们的范围内调用。
您的原始解决方案是唯一可行的解决方案。是的,这是一个痛苦的传递this
,但没有比你不可能的请求更多的详细程度:
this.fill('green');
//or
fill(this,'green');
正如您所看到的,它们在代码中占用的字符数完全相同。
很抱歉,但是你坚持使用它作为解决方案,除非你想创建一个新的命名空间并使它们不私有 - 这只会增加代码量你需要写,即你间接称为“不直接暴露”的东西:
this.myplugin.fill('green');
......更加冗长,因此有点失败了。
Javascript与其他语言不同,本身没有“私人”成员,只有在闭包内可访问的成员,有时可以以类似于私有成员的方式使用,但更多的是“解决方法”,并且不是你正在寻找的“真正的”私人会员。
可能很难与此达成协议(我经常很难),但不尝试将javascript转化为您从其他语言中理解的内容,将其视为它的内容。