使用jQueryUI的widget來寫插件,相比于基本的jquery插件有一些好處: * 方便實(shí)現(xiàn)繼承,代碼重用 * 默認(rèn)是單例 * widget已經(jīng)給你實(shí)現(xiàn)好的一些常用方法,例如destroy 帶來好處的同時(shí)也帶來了荊棘和陷阱,本文的目的就是梳理這些荊棘,標(biāo)出哪里有陷阱。
基本知識(shí):命名規(guī)范,public, private, this, this.element如何開始寫一個(gè)widget呢?模板如下: (function ($) { // utility functions (won’t be inherited) function foo() {} $.widget('命名空間.插件名', $.繼承插件的命名空間.插件名,{ /* snip */ }); })(jQuery); 其中命名空間是可選的,要不要繼承別的widget也是可選的。大頭是后面snip的部分,這也是下文要講的。 一般來說工具函數(shù)寫在widget外面比較合適,但如果你想要這些工具函數(shù)被子類繼承,則需要寫在widget里面。 寫在widget里面的,就有public和private之分,規(guī)則是: public方法首字符不是_ private方法首字符是_
如果我非要在外面調(diào)用private方法,該怎么做?并非一點(diǎn)辦法也沒有: var instance = $('<div>'); instance.mywidget('publicFunction'); // work instance.mywidget('_privateFunction'); // silently fail instance.data('mywidget')._privateFunction(); // work $.mynamespace.mywidget.prototype._privateFunction(); // work 在widget內(nèi),this表示的是什么?我們?cè)趙idget的一個(gè)public函數(shù)內(nèi)用console.log(this)打出來瞧瞧: 日志顯示,this是一個(gè)$.widget.$.(anonymous function).(anonymous function) this.element是變成widget的那個(gè)jQuery對(duì)象,如果要用jquery的方法,往往首先要取到j(luò)query對(duì)象。 this.options是插件的選項(xiàng),下文會(huì)詳解。 this.__proto__包含了插件中定義的所有public和private函數(shù),以及繼承過來的方法。 這里簡(jiǎn)單介紹一下__proto__:每個(gè)對(duì)象都會(huì)在其內(nèi)部初始化一個(gè)屬性,就是__proto__,當(dāng)我們?cè)L問一個(gè)對(duì)象的屬性 時(shí),如果這個(gè)對(duì)象內(nèi)部不存在這個(gè)屬性,那么他就會(huì)去__proto__里找這個(gè)屬性,這個(gè)__proto__又會(huì)有自己的__proto__,于是就這樣 一直找下去,也就是我們平時(shí)所說的原型鏈的概念。 _create _init destroywidget factory實(shí)現(xiàn)了一種單例模式,即不允許在同一個(gè)jQuery對(duì)象上多次實(shí)例化。 當(dāng)調(diào)用$(XX).widgetName()進(jìn)行初始化的時(shí)候,會(huì)執(zhí)行以下代碼(源碼截取自jquery.ui.widget.js): var instance = $.data( this, name ); // 從widget自身取出名字為name的數(shù)據(jù) if ( instance ) { instance.option( options || {} )._init(); // 若該數(shù)據(jù)已經(jīng)存在則只調(diào)用_init } else { $.data( this, name, new object( options, this ) ); // 若數(shù)據(jù)還沒有則新建一個(gè)實(shí)例,并將實(shí)例保存 }
當(dāng)調(diào)用$(XX).widgetName(‘destroy’)進(jìn)行銷毀的時(shí)候,執(zhí)行以下代碼(源碼截取自jquery.ui.widget.js): this.element .unbind( "." + this.widgetName ) .removeData( this.widgetName ); // 刪除在create時(shí)保存的數(shù)據(jù) 有一個(gè)removeData的操作,那么下次調(diào)用$(XX).widgetName()就會(huì)重新實(shí)例化了。
需要注意的是,destroy方法在jquery.ui.widget.js中是有默認(rèn)實(shí)現(xiàn)的,而_create和_init沒有實(shí)現(xiàn)。因此如果用自己的方法覆蓋destroy,不要忘記調(diào)用默認(rèn)的: destory: function () { console.log('destory'); // call the original destroy method since we overwrote it $.Widget.prototype.destroy.call(this); }
以下示例代碼驗(yàn)證_create和_init的區(qū)別以及destroy的作用: var mw = $('#test').myWidget(); // _create _init mw = $('#test').myWidget(); // _init mw.myWidget('destory'); mw = $('#test').myWidget(); // _create _init
那么在_create和_init以及destroy里分別應(yīng)該做什么:_create: 生成HTML,事件綁定。 _init: 執(zhí)行默認(rèn)的初始化動(dòng)作,例如把頁(yè)面變成初始狀態(tài)。 destory: 調(diào)用$.Widget.prototype.destroy.call(this),刪除HTML。
注意:綁定事件要注意給事件名加命名空間后綴:例如 .bind('mouseenter.mywidget', this._hover) options選項(xiàng),在widget中的定義是options,而在調(diào)用時(shí)是option,注意定義的時(shí)候有s,調(diào)用的時(shí)候沒s。 定義: option s : { field1: 'default', function1: function () { console.log('default option function1'); } }, 調(diào)用: $('#test').mywidget('option', 'field1', 2); widget默認(rèn)實(shí)現(xiàn)了兩個(gè)函數(shù):_setOptions和_setOption,_setOptions的實(shí)現(xiàn)就是對(duì)每個(gè)要修改的option調(diào)用_setOption,也就是說真正修改的動(dòng)作在_setOption里。因此,如果要重寫_setOption函數(shù),則一定不要忘記寫: $.Widget.prototype._setOption.apply(this, arguments);
_setOptions和_setOption這倆函數(shù)什么時(shí)候被調(diào)用呢?用下面這個(gè)例子來說明。 例如有這樣的_setOption和_setOptions: _setOption: function (key, value) { console.log('_setOption: key=%s value=%s', key, value); $.Widget.prototype._setOption.apply(this, arguments); }, _setOptions: function (options) { var key; console.group('_setOptions'); for (key in options) { this._setOption(key, options[key]); } console.groupEnd(); return this; }, 以及一個(gè)打印options值的函數(shù)printOptions: printOptions: function () { console.group('options'); console.log('field1: %s', this.options.field1); console.log('function1: %s', this.options.function1); console.groupEnd(); }, 我們像下面這樣調(diào)用: var instance = $('<div>'); // create widget with default options console.group(); instance.mywidget(); instance.mywidget('printOptions'); console.groupEnd(); // create widget with specified options instance.mywidget('destroy'); console.group(); var opts = { field1: 'specified', function1: function () { console.log('specified option function1'); }, }; instance.mywidget(opts); instance.mywidget('printOptions'); console.log('-------------'); instance.mywidget(opts); console.groupEnd(); // modify options console.group(); instance.mywidget('option', 'field1', 2); instance.mywidget('printOptions'); console.groupEnd(); 打出的日志如下: 日志分為三大塊。 第一塊是不使用options來初始化,可以看到直接使用定義里默認(rèn)的options,是不調(diào)用_setOption的。 第二塊是使用options來初始化,這一塊做了兩個(gè)實(shí)驗(yàn)(日志中用--------將兩塊分隔),第一個(gè)實(shí)驗(yàn)是完全重建(_create, _init),從日志可以看到并沒有調(diào)用_setOption;第二個(gè)實(shí)驗(yàn)只是重新初始化(_init),用的options都一樣,從日志可以看到它調(diào)用了_setOption,且在_init之前調(diào)用的。 第三塊不是初始化,而僅僅是修改option值,可以清楚看到調(diào)用了_setOption。
何時(shí)會(huì)調(diào)用_setOption的結(jié)論:1. 像instance.mywidget('option', 'field1', 2); 這樣顯式設(shè)置option時(shí)。 2. 帶著options初始化時(shí):
_trigger注意這個(gè)_trigger是jQueryUI widget factory里的,和jQuery里$.fn命名空間下的trigger函數(shù)不是一個(gè)東西(后者不帶下劃線)。 _trigger一般用來回調(diào)用戶傳入options的callback。 在插件內(nèi)部調(diào)用_trigger(‘myEvent’)即相當(dāng)于調(diào)用options里面的myEvent這個(gè)回調(diào)函數(shù)。 要改動(dòng)options里的event handler應(yīng)該怎么做呢?不要使用bind/unbind,而是去修改options: // bind (overwrite, not add event handler) mw.myWidget('option', 'myEvent', function (event, ui) { console.log('new implement'); }); // unbind mw.myWidget('option', 'myEvent', null);
總結(jié)一下: this._trigger(‘eventName’)是widget特有的,用于調(diào)用options里定義的callback。 this.element.trigger(‘eventName’)是jQuery的,可參考jQuery的事件的用法。(其中this.element是表示該插件的jQuery對(duì)象)
一個(gè)_trigger的樣例: // 模板 this._trigger( "callbackName" , [eventObject], [uiObject] )
// 調(diào)用樣例 this._trigger( "hover", e /* e.type == "mouseenter" */, { hovered: $(e.target)}); // The user can subscribe using an init option $("#elem").filterable( { hover: function(e,ui) { } } ); // Or with traditional event binding/delegation $("#elem").bind( "filterablehover" , function(e,ui) { } );
|
|