用于处理私有函数的jQuery插件设计模式(通用实践?)

时间:2010-01-14 00:53:26

标签: javascript jquery design-patterns plugins

我一直在开发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");
    });

我在其他地方交叉发布我的问题,这也收集了一些有趣的回复:

4 个答案:

答案 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转化为您从其他语言中理解的内容,将其视为它的内容。