使用 jQuery 编写插件的设计建议

孙正业
2023-12-01

设计建议

  1. 为避免 $ 别名与其他库发生冲突,可以使用 jQuery ,或者在立即调用的函数表达式(IIFE)中传入 $ ,使其成为一个局部变量。
  2. 无论是以 $.myPlugin 的方式扩展jQuery,还是以 $.fn.myPlugin 的方式扩展jQuery的原型,给 $ 命名空间添加的属性都不要超过一个。更多的公有方法和属性应该添加到插件的命名空间中(例如, $.myPlugin.publicMethod$.fn.myPlugin.plugin Property )。
  3. 别忘了为插件提供一个默认选项的对象: $.fn.myPlugin.defaults = {size:'large'}
  4. 要允许插件用户有选择地覆盖任何默认选项,包括影响后续方法的调用 ($.fn.myPlugin.defaults.size = 'medium'; )和单独调用( $('div').myPlugin ({size: 'small'}); )。
  5. 多数情况下,扩展jQuery原型时( $.fn.myPlugin )要返回 this ,以便插件用户通过连缀语法调用其他jQuery方法(如 $('div').myPlugin().find('p').addClass('foo') )。
  6. 在扩展jQuery原型时( $.fn.myPlugin ),通过调用 this.each() 强制执行隐式迭代。
  7. 合适的时候,利用回调函数支持灵活地修改插件行为,从而不必修改插件代码。
  8. 如果插件是为了实现用户界面元素,或者需要跟踪元素的状态,使用jQuery UI部件工厂来创建。
  9. 利用QUnit等测试框架为自己的插件维护一组自动的单元测试,以确保插件能够按预期工作。
  10. 使用Git或其他版本控制系统跟踪代码的版本。可以考虑把插件公开托管到Github(http://github.com)上,以便其他人帮你改进。
  11. 在把自己的插件提供给别人使用时,务必明确许可条款。建议考虑使用MIT许可,这也是jQuery使用的许可。
// 阴影效果
;(function($){
    $.fn.shadow = function(opts){

        var options = $.extend({}, $.fn.shadow.defaults, opts);

        return this.each(function(){
            var $originalElement = $(this);

            for(var i = 0; i < options.copies; i++){
                var offset = options.copyOffset(i);

                $originalElement.clone()
                    .css({
                        position: 'absolute',
                        left: $originalElement.offset().left + offset.x,
                        top: $originalElement.offset().top + offset.y,
                        margin: 0,
                        zIndex: -1,
                        opacity: options.opacity
                    }).appendTo('body');
            }
        });
    }
// 可定制的默认设置参数
    $.fn.shadow.defaults = {
        copies: 5,
        opacity: 0.1,
        copyOffset: function(index){
            return {x: index, y:index};
        }
    };
})(jQuery);

1、编写定制的选择符插件

在使用选择符表达式查找元素的时候,jQuery会在一个内部的对象 expr 中取得JavaScript代码。这个对象中的值与我们传入到 .filter() 或 .not() 中的筛选函数非常相似,当且仅当取得的函数返回 true 的情况下,才会让每个元素包含在结果集中。使用 $.extend() 函数可以为这个对象添加新的表达式。

(function($) {
    $.extend($.expr[':'], {
        group: function(element, index, matches, set) { var num = parseInt(matches[3], 10); if (isNaN(num)) { return false; } return index % (num * 2) <num; }
    });
})(jQuery);

以上代码告诉jQuery: group 是一个有效的字符串,可以放在一个冒号的后面构成选择符表达式。而在遇到这个选择符表达式的时候,应该调用给定的函数,用以决定相应的元素是否应该包含在结果集中。
这个被求值的函数一共接收了4个参数。
(1) element :当前考虑的DOM元素。这个参数对于大多数选择符都是必须的,但我们这个选择符则不需要。
(2) index :DOM元素在结果集中的索引。
(3) matches :数组,包含用于解析这个选择符的正则表达式的解析结果。一般来说,matches[3] 是这个数组中唯一有用的值;假设有一个选择符的形式为 :group(b) ,则matches[3] 中包含的值就是 b ,也就是括号中的文本。
(4) set :匹配到当前元素的整个DOM元素集合。这个参数很少用。
伪类选择符需要使用包含在这4个参数中的信息,决定当前元素是否应该包含在结果集中。在我们这个例子中,只需要 index 和 matches 这两个参数。

2、编写DOM遍历方法插件

新添加的jQuery方法应该在操作匹配的元素集之后返回jQuery对象,以便用户可以再连缀其他方法。在创建DOM遍历方法时,这个过程也是类似的,但是返回的jQuery对象必须要指向一个新匹配的元素集。可以通过为 $.fn 添加属性的方式来向jQuery中添加遍历方法。

(function($) {
    $.fn.column = function() {
        var $cells = $();
        this.each(function() { var $td = $(this).closest('td, th'); if ($td.length) { var colNum = $td[0].cellIndex + 1; var $columnCells = $td .closest('table') .find('td, th') .filter(':nth-child(' + colNum + ')'); $cells = $cells.add($columnCells); } });
        return this.pushStack($cells);
    };
})(jQuery);

3、使用jQuery UI 构建插件

如果我们自己要编写的插件会创建新的用户界面元素,通常最好以扩展jQuery UI库的方式来实现。

每个部件都会包含一组复杂的功能,但所幸的是,这们不需要自己承担这些复杂性。jQueryUI库的核心包含了一个工厂方法,叫 $.widget() ,这个方法能帮我们做很多事情。使用这个方法可以确保我们的代码达到所有jQuery UI部件用户认可的API标准。

使用部件工厂创建的插件具有很多不错的特性。只要编写少量代码,就可以额外获得这些功能(甚至更多):
(1) 插件具有了“状态”,可以检测、修改甚至在应用之后完全颠覆插件的原始效果;
(2) 自动将用户提供的选项与定制的选项合并到一起;
(3) 多个插件方法无缝组合为一个jQuery方法,这个方法接受一个表明要调用哪个子方法的字符串;
(4) 插件触发的自定义事件处理程序可以访问部件实例的数据。

事实上,鉴于这些功能如此诱人,在构建任何适当的(无论与UI有关还是无关的)复杂插件时,谁都希望使用部件工厂方法。

// 使用jQuery UI 来创建插件  .tooltip()
(function($) {
    $.widget('ljq.tooltip', {
        _create: function() {
            this._tooltipDiv = $('<div></div>')
                .addClass('ljq-tooltip-text ' +
                'ui-widget ui-state-highlight ui-corner-all')
                .hide().appendTo('body');
            this.element
                .addClass('ljq-tooltip-trigger')
                .on('mouseenter.ljq-tooltip',
                $.proxy(this._open, this))
                .on('mouseleave.ljq-tooltip',
                $.proxy(this._close, this));
        },

        destroy: function() {
            this._tooltipDiv.remove();
            this.element
                .removeClass('ljq-tooltip-trigger')
                .off('.ljq-tooltip');
            $.Widget.prototype.destroy.apply(this, arguments);
        },

        options: {
            offsetX: 10,
            offsetY: 10,
            content: function() {
                return $(this).data('tooltip-text');
            }
        },

        open: function() {
            this._open();
        },

        close: function() {
            this._close();
        },

        _open: function() {
            if (!this.options.disabled) {
                var elementOffset = this.element.offset();
                this._tooltipDiv.css({
                    left: elementOffset.left + this.options.offsetX,
                    top: elementOffset.top + this.element.height()
                    + this.options.offsetY
                }).text(this.options.content.call(this.element[0]));
                this._tooltipDiv.show();
                this._trigger('open');
            }
        },

        _close: function() {
            this._tooltipDiv.hide();
            this._trigger('close');
        }
    });
})(jQuery);
 类似资料: