JQuery扩展的几个疑惑
sshong 发表于2013年6月27日 18:30:35 更新于2013年7月1日 15:00:19
总结以下最近写JQuery扩展的疑惑。

写一个JQuery扩展很容易:
(function($){
    $.fn.TestComponent = function(){
        return this.each(function() {
            console.log('init');
        });
    };
})(jQuery);

这样就行了,调用方法如下:
$('#test').TestComponent();
但是通常一个功能完善的扩展不仅仅是调用一次初始化就完事的,假设是要扩展出一个基本组件,你用上述方式扩展生成了,还需要提供接口供用户访问数据或者侦听事件响应或者回调等。

通常一个组件提供的接口可能如下几种:
1、访问属性(譬如获取组件当前值)
2、分发事件(譬如用户更改值)
3、一些特殊的回调(譬如加载完成等,跟事件类似)
4、设置某些属性或者调用某些动作方法。

问题来了,上面这种写法,调用一次之后返回的就是一个jQuery对象。
对于回调好处理,作为一个初始化参数传进去就可以了,但是要想随时随地访问内部属性以及侦听事件、调用接口,这种写法已经没有机会了。

以下是我总结的几种方法,1 2是我自己原先的考虑,3是jqueryUI的写法:
方法1、把组件变成一个独立类,不再是jQuery的扩展,譬如
(function(window){
    var TestComponent = function(jqueryItem) {
        this.item = jqueryItem;
        this.item.each(function(){
            console.log('init');
        });
        this.name = 'testName';
    };
    TestComponent.prototype.sayHello = function() {
        console.log('hello');
    };

    window.TestComponent = TestComponent;
})(window);
调用的时候就这样
var test = new TestComponent($('#test'));
test.sayHello();
console.log(test.name);
这样的坏处一方面不是通常的jQuery扩展的写法,另一方面还需要用一个变量来存储这个组件实例。

方法2、用jQuery扩展的方式,但是不是返回jQuery对象
(function($){
    var TestComponent = function(jqueryItem) {
        this.item = jqueryItem;
        this.item.each(function(){
            console.log('init');
        });
        this.name = 'testName';
    };
    TestComponent.prototype.sayHello = function() {
        console.log('hello');
    };

    $.fn.TestComponent = function(){
        var test;
        this.each(function() {
            test = new TestComponent($(this));
        });
        return test;
    }
})(jQuery);

var test = $('#test').TestComponent();
test.sayHello();
console.log(test.name);
这样的坏处一方面不能链式调用jQuery对象的其他方法,另一方面也需要用一个变量来存储这个组件实例。

方法3、今天看了下jQueryUI的源码,它里面的扩展写法。
扩展初始化时会把生成的扩展实例保存起来作为element的属性;根据传递的参数判断是否是初始化扩展还是调用扩展的某方法。
(这也是为什么jQueryUI里都是方法,没有属性的原因了)
(function($) {
    var TestComponent = function(jqueryItem) {
        this.item = jqueryItem;
        this.item.each(function(){
            console.log('init');
        });
        this._name = 'testName';
    };
    TestComponent.prototype.sayHello = function() {
        console.log('hello');
    };
    TestComponent.prototype.name = function() {
        return this._name;
    };

    $.fn.TestComponent = function(){
        //init
        if(arguments.length == 0) {
            return this.each(function() {
                this['componentInstance'] = new TestComponent($(this));
            });
        }
        //else call function
        else {
            var methodName = arguments[0];
            var rtn;
            this.each(function(){
                if(this['componentInstance']) {
                    rtn = this['componentInstance'][methodName]();
                }
            });
            return rtn;
        }
    };
})(jQuery);

$('#test').TestComponent();
$('#test').TestComponent('sayHello');
console.log($('#test').TestComponent('name'));
这样一方面可以链式调用jQuery对象的其他方面,另一方面无需再用变量存储组件实例。但是这种写法有点小恶心,回头想想有没有更好的方法,其实我觉得2的方式也未尝不可。

以下摘录一些比较有名的jQuery插件的写法:
1、Magnific-Popup
$.fn.magnificPopup = function(options) {
    _checkInstance();

    var jqEl = $(this);

    // We call some API method of first param is a string
    if (typeof options === "string" ) {

        if(options === 'open') {
            var items,
                itemOpts = _isJQ ? jqEl.data('magnificPopup') : jqEl[0].magnificPopup,
                index = parseInt(arguments[1], 10) || 0;

            if(itemOpts.items) {
                items = itemOpts.items[index];
            } else {
                items = jqEl;
                if(itemOpts.delegate) {
                    items = items.find(itemOpts.delegate);
                }
                items = items.eq( index );
            }
            mfp._openClick({mfpEl:items}, jqEl, itemOpts);
        } else {
            if(mfp.isOpen)
                mfp[options].apply(mfp, Array.prototype.slice.call(arguments, 1));
        }

    } else {

        /*
         * As Zepto doesn't support .data() method for objects
         * and it works only in normal browsers
         * we assign "options" object directly to the DOM element. FTW!
         */
        if(_isJQ) {
            jqEl.data('magnificPopup', options);
        } else {
            jqEl[0].magnificPopup = options;
        }

        mfp.addGroup(jqEl, options);

    }
    return jqEl;
};

2、wookMark
$.fn.wookmark = function(options) {
    // Create a wookmark instance if not available
    if (!this.wookmarkInstance) {
      this.wookmarkInstance = new Wookmark(this, options || {});
    } else {
      this.wookmarkInstance.update(options || {});
    }

    // Apply layout
    this.wookmarkInstance.layout();

    // Display items (if hidden) and return jQuery object to maintain chainability
    return this.show();
  };

以下代码摘录自jqueryUI
1、判断是否初始化还是调用方法
$.widget.bridge = function( name, object ) {
    var fullName = object.prototype.widgetFullName || name;
    $.fn[ name ] = function( options ) {
        var isMethodCall = typeof options === "string",
            args = slice.call( arguments, 1 ),
            returnValue = this;

        // allow multiple hashes to be passed on init
        options = !isMethodCall && args.length ?
            $.widget.extend.apply( null, [ options ].concat(args) ) :
            options;

        if ( isMethodCall ) {
            this.each(function() {
                var methodValue,
                    instance = $.data( this, fullName );
                if ( !instance ) {
                    return $.error( "cannot call methods on " + name + " prior to initialization; " +
                        "attempted to call method '" + options + "'" );
                }
                if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
                    return $.error( "no such method '" + options + "' for " + name + " widget instance" );
                }
                methodValue = instance[ options ].apply( instance, args );
                if ( methodValue !== instance && methodValue !== undefined ) {
                    returnValue = methodValue && methodValue.jquery ?
                        returnValue.pushStack( methodValue.get() ) :
                        methodValue;
                    return false;
                }
            });
        } else {
            this.each(function() {
                var instance = $.data( this, fullName );
                if ( instance ) {
                    instance.option( options || {} )._init();
                } else {
                    $.data( this, fullName, new object( options, this ) );
                }
            });
        }

        return returnValue;
    };
};

2、初始化时绑定实例到element上
$.Widget.prototype = {
    widgetName: "widget",
    widgetEventPrefix: "",
    defaultElement: "<div>",
    options: {
        disabled: false,

        // callbacks
        create: null
    },
    _createWidget: function( options, element ) {
        element = $( element || this.defaultElement || this )[ 0 ];
        this.element = $( element );
        this.uuid = uuid++;
        this.eventNamespace = "." + this.widgetName + this.uuid;
        this.options = $.widget.extend( {},
            this.options,
            this._getCreateOptions(),
            options );

        this.bindings = $();
        this.hoverable = $();
        this.focusable = $();

        if ( element !== this ) {
            $.data( element, this.widgetFullName, this );
            this._on( true, this.element, {
                remove: function( event ) {
                    if ( event.target === element ) {
                        this.destroy();
                    }
                }
            });
            this.document = $( element.style ?
                // element within the document
                element.ownerDocument :
                // element is window or document
                element.document || element );
            this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
        }

        this._create();
        this._trigger( "create", null, this._getCreateEventData() );
        this._init();
    }

3、通用的扩展写法
$.widget("ui.draggable", $.ui.mouse, {
    version: "1.10.3",
    widgetEventPrefix: "drag",
    options: {
        addClasses: true,
        appendTo: "parent",
        axis: false,
        connectToSortable: false,
        containment: false,
        cursor: "auto",
        cursorAt: false,
        grid: false,
        handle: false,
        helper: "original",
        iframeFix: false,
        opacity: false,
        refreshPositions: false,
        revert: false,
        revertDuration: 500,
        scope: "default",
        scroll: true,
        scrollSensitivity: 20,
        scrollSpeed: 20,
        snap: false,
        snapMode: "both",
        snapTolerance: 20,
        stack: false,
        zIndex: false,

        // callbacks
        drag: null,
        start: null,
        stop: null
    },
    _create: function() {

        if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
            this.element[0].style.position = "relative";
        }
        if (this.options.addClasses){
            this.element.addClass("ui-draggable");
        }
        if (this.options.disabled){
            this.element.addClass("ui-draggable-disabled");
        }

        this._mouseInit();

    },
标签:jquery分类:JS&Html5阅读:2566
评论
积木的唠叨2013年6月28日 22:36
一般都是采用第三种插件写法:)
添加评论
您的大名,限长10汉字,20英文(*)
电子信箱(*)
您的网站
正文,限长500汉字,1000英文(*)
验证码(*) 单击刷新验证码
联系我
博客订阅