处理私有函数的jQuery插件设计模式(通用做法?[英] jQuery plugin design pattern (common practice?) for dealing with private functions

本文是小编为大家收集整理的关于处理私有函数的jQuery插件设计模式(通用做法?的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我已经开发了相当长的一段时间了,我想我现在知道如何设计一个.不过,一个问题一直在na,这就是如何以强大而优雅的方式处理私人功能.

我的插件通常看起来像这样:

(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对象. IE.我必须这样称呼:fill(this, 'red');,而我想这样称呼它:this.fill('red');

当然,我们只需将fill放入jQuery.fn中就可以实现这一结果.但这感觉很不舒服.想象一下,根据这种方法开发了十个插件,每个插件将其中五个"私人"功能放入jQuery函数名称空间.最终陷入了一团糟.我们可以通过将这些功能的每个功能都用它们属于的插件的名称前缀来减轻,但这并没有真正使其更具吸引力.这些功能应该是插件的私人,因此我们根本不想将它们暴露于外界(至少不是直接).

所以有我的问题:你们中的任何人都有关于如何获得两全其美的建议.那是;插件代码能够以类似于this.fill('red')(或this.myplugin.fill('red')甚至this.myplugin().fill('red')等)的方式调用"私有"插件函数,同时阻止jQuery函数命名空间污染.当然,这应该是轻量级的,因为这些私人功能可能很频繁.


更新:感谢您的建议.

我特别喜欢David定义具有"私有"功能并包裹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");
    });

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

推荐答案

一件事首先:如果您想调用this.fill('red');之类的东西, 是jQuery的一个实例,则必须扩展jQuery Prototype并制作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名称空间中都不会可用.您可以在私有对象中添加尽可能多的方法,只要您返回 this ,也可以像我在示例中一样链接它们.

.

其他推荐答案

怎么样(在插件范围内):

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');
}

其他推荐答案

您可能想看看 jquery ui widget工厂实现了.

基本方法就是这样:

(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);

本文地址:https://www.itbaoku.cn/post/627660.html

问题描述

I've been developing jQuery plugins for quite some time now, and I like to think I know how to design one well by now. One issue keeps nagging me though, and that is how to deal with private functions in a powerful yet elegant manner.

My plugins generally look something like this:

(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));

Now my question is: how to neatly DRY up that shared functionality? An obvious solution would be to put it in a function within the plugin's namespace:

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

Although this solution is effective and nicely namespaced, I really dislike it. For one simple reason: I have to pass it the jQuery object. I.e. I have to call it like this: fill(this, 'red');, while I would like to call it like this: this.fill('red');

Of course we could achieve this result by simply putting fill into jQuery.fn. But that feels very uncomfortable. Imagine having ten plugins developed based on this approach and each plugin putting five of those 'private' functions into the jQuery function namespace. It ends up in a big mess. We could mitigate by prefixing each of these functions with the name of the plugin they belong to, but that doesn't really make it more attractive. These functions are supposed to be private to the plugin, so we do not want to expose them to the outside world at all (at least not directly).

So there's my question: does anyone of you have suggestions for how to get the best of both worlds. That is; plugin code being able to call 'private' plugin functions in a way similar to this.fill('red') (or this.myplugin.fill('red') or even this.myplugin().fill('red') etc.), while preventing jQuery function namespace pollution. And of course it should be light-weight, as these private functions might be called very frequently.


UPDATE: Thanks for your suggestions.

I especially like David's idea of defining an object type that holds the 'private' functions and wraps a jQuery object. The only problem with it is that it still disallows me from chaining 'private' and 'public' functions. Which was big reason to want a syntax like this.fill('red') to begin with.

I ended up with a solution which I consider not tremendously elegant, but appealing to the 'best of both worlds' cause:

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

Which allows for constructs like:

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

I cross-posted my question in other places, which also collected some interesting responses:

推荐答案

One thing first: if you would like to call something like this.fill('red'); where this is an instance of jQuery, you have to extend the jQuery prototype and make fill() "public". jQuery provides guidelines for extending it's prototype using so called "plugins" that can be added using $.fn.fill, which is the same as jQuery.prototype.fill.

In jQuery callbacks, this is often a reference to the HTML Element, and you can't add prototypes to those (yet). That is one of the reason why jQuery wraps elements and return jQuery instances that can be easily extended.

Using the (function(){})(); syntax, you can create and execute "private" javascript on the fly, and it all disappears when it's done. Using this technique, you can create your own jQuery-like syntax that wraps jQuery into your own private chainable object.

(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'

This way, the P stands for your own toolbox of "private" functions. They won't be available anywhere else in the code or in the jQuery namespace unless you attach them somewhere. You can add as many methods as you like in the Private object, and as long as you return this, you can also chain them jQuery-style as I did in the example.

其他推荐答案

How about (within the plugin's scope):

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

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

That way, fill will retain the jQuery context you're in, and is still private to your plugin


Amended: the above is incorrect w.r.t. scoping, Try the following instead:

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');
}

其他推荐答案

You might want to take a look at how the jQuery UI Widget Factory is implemented.

The basic approach is like this:

(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);