如何使用方法创建 jQuery 插件?

IT技术 javascript jquery jquery-plugins javascript-framework
2021-02-02 15:32:22

我正在尝试编写一个 jQuery 插件,它将为调用它的对象提供额外的功能/方法。我在网上阅读的所有教程(过去2个小时一直在浏览)最多包括如何添加选项,但不包括附加功能。

这是我想要做的:

//通过调用该div的插件将div格式化为消息容器

$("#mydiv").messagePlugin();
$("#mydiv").messagePlugin().saySomething("hello");

或类似的规定。它归结为:我调用插件,然后调用与该插件关联的函数。我似乎无法找到一种方法来做到这一点,而且我以前见过很多插件都这样做过。

到目前为止,这是我对插件的了解:

jQuery.fn.messagePlugin = function() {
  return this.each(function(){
    alert(this);
  });

  //i tried to do this, but it does not seem to work
  jQuery.fn.messagePlugin.saySomething = function(message){
    $(this).html(message);
  }
};

我怎样才能实现这样的目标?

谢谢!


2013 年 11 月 18 日更新:我已将正确答案更改为 Hari 的以下评论和点赞。

6个回答

根据 jQuery 插件创作页面 ( http://docs.jquery.com/Plugins/Authoring ),最好不要混淆 jQuery 和 jQuery.fn 命名空间。他们建议使用这种方法:

(function( $ ){

    var methods = {
        init : function(options) {

        },
        show : function( ) {    },// IS
        hide : function( ) {  },// GOOD
        update : function( content ) {  }// !!!
    };

    $.fn.tooltip = function(methodOrOptions) {
        if ( methods[methodOrOptions] ) {
            return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
            // Default to "init"
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.tooltip' );
        }    
    };


})( jQuery );

基本上,您将函数存储在一个数组中(范围限定为包装函数),如果传递的参数是字符串,则检查条目,如果参数是对象(或 null),则恢复为默认方法(此处为“init”)。

然后你可以像这样调用方法......

$('div').tooltip(); // calls the init method
$('div').tooltip({  // calls the init method
  foo : 'bar'
});
$('div').tooltip('hide'); // calls the hide method
$('div').tooltip('update', 'This is the new tooltip content!'); // calls the update method

Javascripts“参数”变量是一个包含所有传递参数的数组,因此它可以处理任意长度的函数参数。

非常方便的架构。我还在调用 init 方法之前添加了这一行:this.data('tooltip', $.extend(true, {}, $.fn.tooltip.defaults, methodOrOptions));,所以现在我可以在初始化后随时访问选项。
2021-03-17 15:32:22
@DiH,我和你在一起。这种方法看起来很棒,但它不能让您从init.
2021-03-25 15:32:22
这种技术有一个大问题!它不会像您认为的那样为选择器中的每个元素创建一个新实例,而是只创建一个附加到选择器本身的实例。查看我的答案以获取解决方案。
2021-03-31 15:32:22
这是我使用的方法。您还可以通过 $.fn.tooltip('methodname', params); 静态调用这些方法。
2021-04-03 15:32:22
对于像我这样首先说“参数变量从何而来”的人 - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... - 我一直在使用 JS,但从来不知道这一点。你每天学习新的东西!
2021-04-11 15:32:22

这是我用于使用其他方法创建插件的模式。你会像这样使用它:

$('selector').myplugin( { key: 'value' } );

或者,直接调用一个方法,

$('selector').myplugin( 'mymethod1', 'argument' );

例子:

;(function($) {

    $.fn.extend({
        myplugin: function(options,arg) {
            if (options && typeof(options) == 'object') {
                options = $.extend( {}, $.myplugin.defaults, options );
            }

            // this creates a plugin for each element in
            // the selector or runs the function once per
            // selector.  To have it do so for just the
            // first element (once), return false after
            // creating the plugin to stop the each iteration 
            this.each(function() {
                new $.myplugin(this, options, arg );
            });
            return;
        }
    });

    $.myplugin = function( elem, options, arg ) {

        if (options && typeof(options) == 'string') {
           if (options == 'mymethod1') {
               myplugin_method1( arg );
           }
           else if (options == 'mymethod2') {
               myplugin_method2( arg );
           }
           return;
        }

        ...normal plugin actions...

        function myplugin_method1(arg)
        {
            ...do method1 with this and arg
        }

        function myplugin_method2(arg)
        {
            ...do method2 with this and arg
        }

    };

    $.myplugin.defaults = {
       ...
    };

})(jQuery);
这似乎是一种非标准的做事方式 - 有没有比这更简单的事情,比如链接函数?谢谢!
2021-03-12 15:32:22
;你的第一行是什么意思请向我解释:)
2021-03-16 15:32:22
与 jquery-ui 相同的模式,我不喜欢所有的魔法字符串,但还有其他方法吗!
2021-03-20 15:32:22
@yuval——通常 jQuery 插件返回 jQuery 或一个值,而不是插件本身。这就是为什么当您想要调用插件时,方法的名称作为参数传递给插件的原因。您可以传递任意数量的参数,但您必须调整函数和参数解析。如您所示,最好将它们设置在匿名对象中。
2021-03-28 15:32:22
@GusDeCooL 它只是确保我们开始一个新的语句,以便我们的函数定义不会被解释为其他人格式错误的 Javascript 的参数(即,初始括号不被视为函数调用运算符)。参见stackoverflow.com/questions/7365172/...
2021-03-29 15:32:22

这种方法怎么样:

jQuery.fn.messagePlugin = function(){
    var selectedObjects = this;
    return {
             saySomething : function(message){
                              $(selectedObjects).each(function(){
                                $(this).html(message);
                              });
                              return selectedObjects; // Preserve the jQuery chainability 
                            },
             anotherAction : function(){
                               //...
                               return selectedObjects;
                             }
           };
}
// Usage:
$('p').messagePlugin().saySomething('I am a Paragraph').css('color', 'red');

选定的对象存储在 messagePlugin 闭包中,该函数返回一个包含与插件关联的函数的对象,在每个函数中,您可以对当前选定的对象执行所需的操作。

您可以在此处测试和使用代码

编辑:更新代码以保留 jQuery 可链接性的强大功能。

也许这应该是最好的答案
2021-03-22 15:32:22
这种方法的主要问题是它不能保持可链接性,$('p').messagePlugin()除非您调用它返回的两个函数之一。
2021-03-22 15:32:22
不过,这有点打破了 jQuery 的可链接性范式。
2021-03-31 15:32:22
每次调用 messagePlugin() 时,它都会使用这两个函数创建一个新对象,不是吗?
2021-03-31 15:32:22
我很难理解这会是什么样子。假设我有第一次运行时需要执行的代码,我必须首先在我的代码中初始化它 - 像这样: $('p').messagePlugin(); 然后稍后在代码中我想调用函数 saySomething 像这样 $('p').messagePlugin().saySomething('something'); 这不会重新初始化插件然后调用函数吗?外壳和选项会是什么样子?非常感谢你。-尤瓦尔
2021-04-07 15:32:22

当前选择的答案的问题在于,您实际上并没有像您认为的那样为选择器中的每个元素创建自定义插件的新实例……您实际上只是创建了一个实例并传入选择器本身作为范围。

查看这个小提琴以获得更深入的解释。

相反,您需要使用jQuery.each循环选择器,并为选择器中的每个元素实例化自定义插件的新实例。

就是这样:

(function($) {

    var CustomPlugin = function($el, options) {

        this._defaults = {
            randomizer: Math.random()
        };

        this._options = $.extend(true, {}, this._defaults, options);

        this.options = function(options) {
            return (options) ?
                $.extend(true, this._options, options) :
                this._options;
        };

        this.move = function() {
            $el.css('margin-left', this._options.randomizer * 100);
        };

    };

    $.fn.customPlugin = function(methodOrOptions) {

        var method = (typeof methodOrOptions === 'string') ? methodOrOptions : undefined;

        if (method) {
            var customPlugins = [];

            function getCustomPlugin() {
                var $el          = $(this);
                var customPlugin = $el.data('customPlugin');

                customPlugins.push(customPlugin);
            }

            this.each(getCustomPlugin);

            var args    = (arguments.length > 1) ? Array.prototype.slice.call(arguments, 1) : undefined;
            var results = [];

            function applyMethod(index) {
                var customPlugin = customPlugins[index];

                if (!customPlugin) {
                    console.warn('$.customPlugin not instantiated yet');
                    console.info(this);
                    results.push(undefined);
                    return;
                }

                if (typeof customPlugin[method] === 'function') {
                    var result = customPlugin[method].apply(customPlugin, args);
                    results.push(result);
                } else {
                    console.warn('Method \'' + method + '\' not defined in $.customPlugin');
                }
            }

            this.each(applyMethod);

            return (results.length > 1) ? results : results[0];
        } else {
            var options = (typeof methodOrOptions === 'object') ? methodOrOptions : undefined;

            function init() {
                var $el          = $(this);
                var customPlugin = new CustomPlugin($el, options);

                $el.data('customPlugin', customPlugin);
            }

            return this.each(init);
        }

    };

})(jQuery);

和一个工作小提琴

您会注意到在第一个小提琴中,所有 div 总是向右移动完全相同的像素数。这是因为选择器中的所有元素都只存在一个选项对象。

使用上面编写的技术,您会注意到在第二个小提琴中,每个 div 都没有对齐并且是随机移动的(不包括第一个 div,因为它的随机化器在第 89 行始终设置为 1)。那是因为我们现在正在为选择器中的每个元素正确实例化一个新的自定义插件实例。每个元素都有自己的选项对象,并不保存在选择器中,而是保存在自定义插件本身的实例中。

这意味着您将能够从新的 jQuery 选择器访问在 DOM 中的特定元素上实例化的自定义插件的方法,并且不会像您在第一个小提琴中那样被迫缓存它们。

例如,这将使用第二个小提琴中的技术返回所有选项对象的数组。它会在第一个返回 undefined 。

$('div').customPlugin();
$('div').customPlugin('options'); // would return an array of all options objects

这就是您必须访问第一个小提琴中的选项对象的方式,并且只会返回一个对象,而不是它们的数组:

var divs = $('div').customPlugin();
divs.customPlugin('options'); // would return a single options object

$('div').customPlugin('options');
// would return undefined, since it's not a cached selector

我建议使用上述技术,而不是当前选择的答案中的技术。

使用这种方法的jQuery chainability是不工作... $('.my-elements').find('.first-input').customPlugin('update'‌​, 'first value').end().find('.second-input').customPlugin('update', 'second value'); returns Cannot read property 'end' of undefinedjsfiddle.net/h8v1k2pL
2021-03-24 15:32:22
谢谢,这对我帮助很大,特别是向我介绍了 .data() 方法。非常便利。FWIW 您还可以通过使用匿名方法来简化您的一些代码。
2021-04-02 15:32:22

随着Widget Factory的引入,jQuery 使这变得更加容易

例子:

$.widget( "myNamespace.myPlugin", {

    options: {
        // Default options
    },

    _create: function() {
        // Initialization logic here
    },

    // Create a public method.
    myPublicMethod: function( argument ) {
        // ...
    },

    // Create a private method.
    _myPrivateMethod: function( argument ) {
        // ...
    }

});

初始化:

$('#my-element').myPlugin();
$('#my-element').myPlugin( {defaultValue:10} );

方法调用:

$('#my-element').myPlugin('myPublicMethod', 20);

(这就是jQuery UI库的构建方式。)

a) 那是争论,b) 每个更好的 JS IDE 都有代码完成或 linting,c) 谷歌它
2021-03-12 15:32:22
根据文档:该系统称为 Widget Factory,并作为 jQuery UI 1.8 的一部分公开为 jQuery.widget;但是,它可以独立于 jQuery UI 使用。如何在没有jQuery UI 的情况下使用 $.widget
2021-03-26 15:32:22
@daniel.sedlacek a) “非常糟糕的架构” - 它是 jQuery 的标准小部件架构 b) “在编译时检查完整性” - JavaScript 是一种动态语言 c) “TypeScript” - 哇?
2021-04-01 15:32:22
那纯粹是错觉,塞德拉塞克先生。
2021-04-07 15:32:22