jQuery庫中包括很多函數(shù),所以開發(fā)插件的時(shí)候往往就需要注意命名空間和私有作用域等方面,下面就由Shawn Khameneh老司機(jī)帶你解讀jQuery插件開發(fā)流程及相關(guān)注意點(diǎn).
jquery插件開發(fā)模式
jquery插件一般有三種開發(fā)方式:
通過$.extend()來擴(kuò)展jQuery
通過$.fn 向jQuery添加新的方法
通過$.widget()應(yīng)用jQuery UI的部件工廠方式創(chuàng)建
第一種$.extend()相對簡單,一般很少能夠獨(dú)立開發(fā)復(fù)雜插件,第三種是一種高級的開發(fā)模式,本文也不做介紹。第二種則是一般插件開發(fā)用到的方式,本文著重講講第二種。
插件開發(fā)
第二種插件開發(fā)方式一般是如下定義
$.fn.pluginName = function() {
//your code here
}
插件開發(fā),我們一般運(yùn)用面向?qū)ο蟮乃季S方式
例如定義一個(gè)對象
var Haorooms= function(el, opt) {
this.$element = el,
this.defaults = {
'color': 'red',
'fontSize': '12px',
'textDecoration':'none'
},
this.options = $.extend({}, this.defaults, opt)
}
//定義haorooms的方法
haorooms.prototype = {
changecss: function() {
return this.$element.css({
'color': this.options.color,
'fontSize': this.options.fontSize,
'textDecoration': this.options.textDecoration
});
}
}
$.extend({}, this.defaults, opt)有{}主要是為了創(chuàng)建一個(gè)新對象,保留對象的默認(rèn)值。
$.fn.myPlugin = function(options) {
//創(chuàng)建haorooms的實(shí)體
var haorooms= new Haorooms(this, options);
//調(diào)用其方法
return Haorooms.changecss();
}
調(diào)用這個(gè)插件直接如下就可以
$(function() {
$('a').myPlugin({
'color': '#2C9929',
'fontSize': '20px'
});
})
上述開發(fā)方法的問題
上面的開發(fā)方法存在一個(gè)嚴(yán)重的問題,就是定義了一個(gè)全局的Haorooms,這樣對于插件的兼容等等各個(gè)方面都不好。萬一別的地方用到了Haorooms,那么你的代碼就悲催了!現(xiàn)在我們把上面的代碼包裝起來,用一個(gè)自調(diào)用匿名函數(shù)(有時(shí)又叫塊級作用域或者私有作用域)包裹,就不會(huì)出現(xiàn)這個(gè)問題了!包括js插件的開發(fā),也是一樣的,我們用一個(gè)自調(diào)用匿名函數(shù)把自己寫的代碼包裹起來,就可以了!包裹方法如下:
(function(){
})()
用上面的這個(gè)包裹起來,就可以了。
但是還有一個(gè)問題,當(dāng)我們研究大牛的代碼的時(shí)候,前面經(jīng)??吹接小?;”,那是為了避免代碼合并等不必要的錯(cuò)誤。
例如,我們隨便定義一個(gè)函數(shù):
var haoroomsblog=function(){
}
(function(){
})()
由于haoroomsblog這個(gè)函數(shù)后面沒有加分號(hào),導(dǎo)致代碼出錯(cuò),為了避免這類情況的發(fā)生,通常這么寫!
;(function(){
})()
把你的插件代碼包裹在上面里面,就是一個(gè)簡單的插件了。(注js插件和jquery插件都是如此)
還有一個(gè)問題
把你的插件包裹在
;(function(){
})()
基本上可以說是完美了。但是為了讓你開發(fā)的插件應(yīng)用更加廣泛,兼容性更加好,還要考慮到用插件的人的一些特殊的做法,例如,有些朋友為了避免jquery和zeptojs沖突,將jquery的前綴“$”,修改為“jQuery”,還有些朋友將默認(rèn)的document等方法修改。為了讓你的插件在這些東西修了了的情況下照常運(yùn)行,那么我們的做法是,把代碼包裹在如下里面:
;(function($,window,document,undefined){
//我們的代碼。。
})(jQuery,window,document);
就可以避免上面的一些情況了!
優(yōu)秀的示例
下面,我們有了一個(gè)插件的基本層次:
(function($) {
var privateFunction = function() {
// 代碼在這里運(yùn)行
}
var methods = {
init: function(options) {
return this.each(function() {
var $this = $(this);
var settings = $this.data('pluginName');
if(typeof(settings) == 'undefined') {
var defaults = {
propertyName: 'value',
onSomeEvent: function() {}
}
settings = $.extend({}, defaults, options);
$this.data('pluginName', settings);
} else {
settings = $.extend({}, settings, options);
}
// 代碼在這里運(yùn)行
});
},
destroy: function(options) {
return $(this).each(function() {
var $this = $(this);
$this.removeData('pluginName');
});
},
val: function(options) {
var someValue = this.eq(0).html();
return someValue;
}
};
$.fn.pluginName = function() {
var method = arguments[0];
if(methods[method]) {
method = methods[method];
arguments = Array.prototype.slice.call(arguments, 1);
} else if( typeof(method) == 'object' || !method ) {
method = methods.init;
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.pluginName' );
return this;
}
return method.apply(this, arguments);
}
})(jQuery);
你可能會(huì)注意到,我所提到代碼的結(jié)構(gòu)和其他插件代碼有很大的不同。根據(jù)你的使用和需求的不同,插件的開發(fā)方式也可能會(huì)呈現(xiàn)多樣化。我的目的是澄清代碼中的一些概念,足夠讓你找到適合自己的方法去理解和開發(fā)一個(gè)jQuery插件。
現(xiàn)在,來解剖我們的代碼吧!
容器:一個(gè)即時(shí)執(zhí)行函數(shù)
根本上來說,每個(gè)插件的代碼是被包含在一個(gè)即時(shí)執(zhí)行的函數(shù)當(dāng)中,如下:
(function(arg1, arg2) {
// 代碼
})(arg1, arg2);
即時(shí)執(zhí)行函數(shù),顧名思義,是一個(gè)函數(shù)。讓它與眾不同的是,它被包含在一對小括號(hào)里面,這讓所有的代碼都在匿名函數(shù)的局部作用域中運(yùn)行。這并不是說DOM(全局變量)在函數(shù)內(nèi)是被屏蔽的,而是外部無法訪問到函數(shù)內(nèi)部的公共變量和對象命名空間。這是一個(gè)很好的開始,這樣你聲明你的變量和對象的時(shí)候,就不用擔(dān)心著變量名和已經(jīng)存在的代碼有沖突。
現(xiàn)在,因?yàn)楹瘮?shù)內(nèi)部所有的所有公共變量是無法訪問的,這樣要把jQuery本身作為一個(gè)內(nèi)部的公共變量來使用就會(huì)成為問題。就像普通的函數(shù)一樣,即時(shí)函數(shù)也根據(jù)引用傳入對象參數(shù)。我們可以將jQuery對象傳入函數(shù),如下:
(function($) {
// 局部作用域中使用$來引用jQuery
})(jQuery);
我們傳入了一個(gè)把公共變量“jQuery”傳入了一個(gè)即時(shí)執(zhí)行的函數(shù)里面,在函數(shù)局部(容器)中我們可以通過“$”來引用它。也就是說,我們把容器當(dāng)做一個(gè)函數(shù)來調(diào)用,而這個(gè)函數(shù)的參數(shù)就是jQuery。因?yàn)槲覀円玫摹癹Query”作為公共變量傳入,而不是它的簡寫“$”,這樣我們就可以兼容Prototype庫。如果你不用Prototype或者其它用“$”做簡寫的庫的話,你不這樣做也不會(huì)造成什么影響,但是知道這種用法仍是一件好事。
插件:一個(gè)函數(shù)
一個(gè)jQuery插件本質(zhì)上是我們?nèi)M(jìn)jQuery命名空間中一個(gè)龐大的函數(shù),當(dāng)然,我們可以很輕易地用“jQuery.pluginName=function”,來達(dá)到我們的目的,但是如果我們這樣做的話我們的插件的代碼是處于沒有被保護(hù)的暴露狀態(tài)的?!癹Query.fn”是“jQuery.prototype”的簡寫,意味當(dāng)我們通過jQuery命名空間去獲取我們的插件的時(shí)候,它僅可寫(不可修改)。它事實(shí)上可以為你干點(diǎn)什么事呢?它讓你恰當(dāng)?shù)亟M織自己的代碼,和理解如何保護(hù)你的代碼不受運(yùn)行時(shí)候不需要的修改。最好的說法就是,這是一個(gè)很好的實(shí)踐!
通過一個(gè)插件,我們獲得一個(gè)基本的jQuery函數(shù):
(function($) {
$.fn.pluginName = function(options) {
// 代碼在此處運(yùn)行
return this;
}
})(jQuery);
上面的代碼中的函數(shù)可以像其他的jQuery函數(shù)那樣通過“$(‘#element').pluginName()”來調(diào)用。注意,我是如何把“return this”語句加進(jìn)去的;這小片的代碼通過返回一個(gè)原來元素的集合(包含在this當(dāng)中)的引用來產(chǎn)生鏈?zhǔn)秸{(diào)用的效果,而這些元素是被一個(gè)jQuery對象所包裹的。你也應(yīng)該注意,“this”在這個(gè)特定的作用域中是一個(gè)jQuery對象,相當(dāng)于“$(‘#element')”。
根據(jù)返回的對象,我們可以總結(jié)出,在上面的代碼中,使用“$(‘#element').pluginName()”的效果和使用“$(‘#element')”的效果是一樣的。在你的即時(shí)執(zhí)行函數(shù)作用域中,沒必要用“$(this)”的方式來把this包裹到一個(gè)jQuery對象中,因?yàn)閠his本身已經(jīng)是被包裝好的jQuery對象。
多個(gè)元素:理解Sizzle
jQuery使用的選擇器引擎叫Sizzle,Sizzle可以為你的函數(shù)提供多元素操作(例如對所有類名相同的元素)。這是jQuery幾個(gè)優(yōu)秀的特性之一,但這也是你在開發(fā)插件過程中需要考慮的事情。即使你不準(zhǔn)備為你的插件提供多元素支持,但為這做準(zhǔn)備仍然是一個(gè)很好的實(shí)踐。
這里我添加了一小段代碼,它讓你的插件代碼為多元素集合中每個(gè)元素單獨(dú)地起作用:
function($) {
// 向jQuery中被保護(hù)的“fn”命名空間中添加你的插件代碼,用“pluginName”作為插件的函數(shù)名稱
$.fn.pluginName = function(options) {
// 返回“this”(函數(shù)each()的返回值也是this),以便進(jìn)行鏈?zhǔn)秸{(diào)用。
return this.each(function() {
// 此處運(yùn)行代碼,可以通過“this”來獲得每個(gè)單獨(dú)的元素
// 例如: $(this).show();
var $this = $(this);
});
}
})(jQuery);
在以上示例代碼中,我并不是用 each()在我的選擇器中每個(gè)元素上運(yùn)行代碼。在那個(gè)被 each()調(diào)用的函數(shù)的局部作用域中,你可以通過this來引用每個(gè)被單獨(dú)處理的元素,也就是說你可以通過$(this)來引用它的jQuery對象。在局部作用域中,我用$this變量存儲(chǔ)起jQuery對象,而不是每次調(diào)用函數(shù)的時(shí)候都使用$(this),這會(huì)是個(gè)很好的實(shí)踐。當(dāng)然,這樣做并不總是必要的;但我已經(jīng)額外把它包含在我的代碼中。還有要注意的是,我們將會(huì)對每個(gè)單獨(dú)方法都使用 each(),這樣到時(shí)我們就可以返回我們需要的值,而不是一個(gè)jQuery對象。
下面是一個(gè)例子,假如我們的插件支持一個(gè) val 的方法,它可以返回我們需要的值:
$('#element').pluginName('val');
// 會(huì)返回我們需要的值,而不是一個(gè)jQuery對象
功能:公有方法和私有方法
一個(gè)基本的函數(shù)可能在某些情況下可以良好地工作,但是一個(gè)稍微復(fù)雜一點(diǎn)的插件就需要提供各種各樣的方法和私有函數(shù)。你可能會(huì)使用不同的命名空間去為你的插件提供各種方法,但是最好不要讓你的源代碼因?yàn)槎嘤嗟拿臻g而變得混亂。
下面的代碼定義了一個(gè)存儲(chǔ)公有方法的JSON對象,以及展示了如何使用插件中的主函數(shù)中去判斷哪些方法被調(diào)用,和如何在讓方法作用到選擇器每個(gè)元素上。
(function($) {
// 在我們插件容器內(nèi),創(chuàng)造一個(gè)公共變量來構(gòu)建一個(gè)私有方法
var privateFunction = function() {
// code here
}
// 通過字面量創(chuàng)造一個(gè)對象,存儲(chǔ)我們需要的共有方法
var methods = {
// 在字面量對象中定義每個(gè)單獨(dú)的方法
init: function() {
// 為了更好的靈活性,對來自主函數(shù),并進(jìn)入每個(gè)方法中的選擇器其中的每個(gè)單獨(dú)的元素都執(zhí)行代碼
return this.each(function() {
// 為每個(gè)獨(dú)立的元素創(chuàng)建一個(gè)jQuery對象
var $this = $(this);
// 執(zhí)行代碼
// 例如: privateFunction();
});
},
destroy: function() {
// 對選擇器每個(gè)元素都執(zhí)行方法
return this.each(function() {
// 執(zhí)行代碼
});
}
};
$.fn.pluginName = function() {
// 獲取我們的方法,遺憾的是,如果我們用function(method){}來實(shí)現(xiàn),這樣會(huì)毀掉一切的
var method = arguments[0];
// 檢驗(yàn)方法是否存在
if(methods[method]) {
// 如果方法存在,存儲(chǔ)起來以便使用
// 注意:我這樣做是為了等下更方便地使用each()
method = methods[method];
// 如果方法不存在,檢驗(yàn)對象是否為一個(gè)對象(JSON對象)或者method方法沒有被傳入
} else if( typeof(method) == 'object' || !method ) {
// 如果我們傳入的是一個(gè)對象參數(shù),或者根本沒有參數(shù),init方法會(huì)被調(diào)用
method = methods.init;
} else {
// 如果方法不存在或者參數(shù)沒傳入,則報(bào)出錯(cuò)誤。需要調(diào)用的方法沒有被正確調(diào)用
$.error( 'Method ' + method + ' does not exist on jQuery.pluginName' );
return this;
}
// 調(diào)用我們選中的方法
// 再一次注意我們是如何將each()從這里轉(zhuǎn)移到每個(gè)單獨(dú)的方法上的
return method.call(this);
}
})(jQuery);
注意我把 privateFunction 當(dāng)做了一個(gè)函數(shù)內(nèi)部的全局變量。考慮到所有的代碼的運(yùn)行都是在插件容器內(nèi)進(jìn)行的,所以這種做法是可以被接受的,因?yàn)樗辉诓寮淖饔糜蛑锌捎?。在插件中的主函?shù)中,我檢驗(yàn)了傳入?yún)?shù)所指向的方法是否存在。如果方法不存在或者傳入的是參數(shù)為對象, init 方法會(huì)被運(yùn)行。最后,如果傳入的參數(shù)不是一個(gè)對象而是一個(gè)不存在的方法,我們會(huì)報(bào)出一個(gè)錯(cuò)誤信息。
同樣要注意的是,我是如何在每個(gè)方法中都使用 this.each() 的。當(dāng)我們在主函數(shù)中調(diào)用 method.call(this) 的時(shí)候,這里的 this 事實(shí)上就是一個(gè)jQuery對象,作為 this 傳入每個(gè)方法中。所以在我們方法的即時(shí)作用域中,它已經(jīng)是一個(gè)jQuery對象。只有在被 each()所調(diào)用的函數(shù)中,我們才有必要將this包裝在一個(gè)jQuery對象中。
下面是一些用法的例子:
/*
注意這些例子可以在目前的插件代碼中正確運(yùn)行,并不是所有的插件都使用同樣的代碼結(jié)構(gòu)
*/
// 為每個(gè)類名為 ".className" 的元素執(zhí)行init方法
$('.className').pluginName();
$('.className').pluginName('init');
$('.className').pluginName('init', {}); // 向init方法傳入“{}”對象作為函數(shù)參數(shù)
$('.className').pluginName({}); // 向init方法傳入“{}”對象作為函數(shù)參數(shù)
// 為每個(gè)類名為 “.className” 的元素執(zhí)行destroy方法
$('.className').pluginName('destroy');
$('.className').pluginName('destroy', {}); // 向destroy方法傳入“{}”對象作為函數(shù)參數(shù)
// 所有代碼都可以正常運(yùn)行
$('.className').pluginName('init', 'argument1', 'argument2'); // 把 "argument 1" 和 "argument 2" 傳入 "init"
// 不正確的使用
$('.className').pluginName('nonexistantMethod');
$('.className').pluginName('nonexistantMethod', {});
$('.className').pluginName('argument 1'); // 會(huì)嘗試調(diào)用 "argument 1" 方法
$('.className').pluginName('argument 1', 'argument 2'); // 會(huì)嘗試調(diào)用 "argument 1" ,“argument 2”方法
$('.className').pluginName('privateFunction'); // 'privateFunction' 不是一個(gè)方法
在上面的例子中多次出現(xiàn)了 {} ,表示的是傳入方法中的參數(shù)。在這小節(jié)中,上面代碼可以可以正常運(yùn)行,但是參數(shù)不會(huì)被傳入方法中。繼續(xù)閱讀下一小節(jié),你會(huì)知道如何向方法傳入?yún)?shù)。 設(shè)置插件:傳入?yún)?shù) 許多插件都支持參數(shù)傳入,如配置參數(shù)和回調(diào)函數(shù)。你可以通過傳入JS鍵值對對象或者函數(shù)參數(shù),為方法提供信息。如果你的方法支持多于一個(gè)或兩個(gè)參數(shù),那么沒有比傳入對象參數(shù)更恰當(dāng)?shù)姆绞健?/P>
(function($) {
var methods = {
init: function(options) {
// 在每個(gè)元素上執(zhí)行方法
return this.each(function() {
var $this = $(this);
// 創(chuàng)建一個(gè)默認(rèn)設(shè)置對象
var defaults = {
propertyName: 'value',
onSomeEvent: function() {}
}
// 使用extend方法從options和defaults對象中構(gòu)造出一個(gè)settings對象
var settings = $.extend({}, defaults, options);
// 執(zhí)行代碼
});
}
};
$.fn.pluginName = function() {
var method = arguments[0];
if(methods[method]) {
method = methods[method];
// 我們的方法是作為參數(shù)傳入的,把它從參數(shù)列表中刪除,因?yàn)檎{(diào)用方法時(shí)并不需要它
arguments = Array.prototype.slice.call(arguments, 1);
} else if( typeof(method) == 'object' || !method ) {
method = methods.init;
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.pluginName' );
return this;
}
// 用apply方法來調(diào)用我們的方法并傳入?yún)?shù)
return method.apply(this, arguments);
}
})(jQuery);
正如上面所示,一個(gè)“options”參數(shù)被添加到方法當(dāng)中,和“arguments”也被添加到了主函數(shù)中。如果一個(gè)方法已經(jīng)被聲明,在參數(shù)傳入方法之前,調(diào)用那個(gè)方法的參數(shù)會(huì)從參數(shù)列表中刪除掉。我用了“apply()”來代替了“call()”,“apply()”本質(zhì)上是和“call()”做著同樣的工作的,但不同的是它允許參數(shù)的傳入。這種結(jié)構(gòu)也允許多個(gè)參數(shù)的傳入,如果你愿意這樣做,你也可以為你的方法修改參數(shù)列表,例如:“init:function(arg1, arg2){}”。
如果你是使用JS對象作為參數(shù)傳入,你可能需要定義一個(gè)默認(rèn)對象。一旦默認(rèn)對象被聲明,你可以使用“$.extend”來合并參數(shù)對象和默認(rèn)對象中的值,以形成一個(gè)新的參數(shù)對象來使用(在我們的例子中就是“settings”);
這里有一些例子,用來演示以上的邏輯:
var options = {
customParameter: 'Test 1',
propertyName: 'Test 2'
}
var defaults = {
propertyName: 'Test 3',
onSomeEvent: 'Test 4'
}
var settings = $.extend({}, defaults, options);
/*
settings == {
propertyName: 'Test 2',
onSomeEvent: 'Test 4',
customParameter: 'Test 1'
}
*/
保存設(shè)置:添加持久性數(shù)據(jù)
有時(shí)你會(huì)想在你的插件中保存設(shè)置和信息,這時(shí)jQuery中的“data()”函數(shù)就可以派上用場了。它在使用上是非常簡單的,它會(huì)嘗試獲取和元素相關(guān)的數(shù)據(jù),如果數(shù)據(jù)不存在,它就會(huì)創(chuàng)造相應(yīng)的數(shù)據(jù)并添加到元素上。一旦你使用了“data()”來為元素添加信息,請確認(rèn)你已經(jīng)記住,當(dāng)不再需要數(shù)據(jù)的時(shí)候,用“removeData()”來刪除相應(yīng)的數(shù)據(jù)。
(function($) {
var privateFunction = function() {
// 執(zhí)行代碼
}
var methods = {
init: function(options) {
// 在每個(gè)元素上執(zhí)行方法
return this.each(function() {
var $this = $(this);
// 嘗試去獲取settings,如果不存在,則返回“undefined”
var settings = $this.data('pluginName');
// 如果獲取settings失敗,則根據(jù)options和default創(chuàng)建它
if(typeof(settings) == 'undefined') {
var defaults = {
propertyName: 'value',
onSomeEvent: function() {}
}
settings = $.extend({}, defaults, options);
// 保存我們新創(chuàng)建的settings
$this.data('pluginName', settings);
} else {
/ 如果我們獲取了settings,則將它和options進(jìn)行合并(這不是必須的,你可以選擇不這樣做)
settings = $.extend({}, settings, options);
// 如果你想每次都保存options,可以添加下面代碼:
// $this.data('pluginName', settings);
}
// 執(zhí)行代碼
});
},
destroy: function(options) {
// 在每個(gè)元素中執(zhí)行代碼
return $(this).each(function() {
var $this = $(this);
// 執(zhí)行代碼
// 刪除元素對應(yīng)的數(shù)據(jù)
$this.removeData('pluginName');
});
},
val: function(options) {
// 這里的代碼通過.eq(0)來獲取選擇器中的第一個(gè)元素的,我們或獲取它的HTML內(nèi)容作為我們的返回值
var someValue = this.eq(0).html();
// 返回值
return someValue;
}
};
$.fn.pluginName = function() {
var method = arguments[0];
if(methods[method]) {
method = methods[method];
arguments = Array.prototype.slice.call(arguments, 1);
} else if( typeof(method) == 'object' || !method ) {
method = methods.init;
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.pluginName' );
return this;
}
return method.apply(this, arguments);
}
})(jQuery);
在上面的代碼中,我檢驗(yàn)了元素的數(shù)據(jù)是否存在。如果數(shù)據(jù)不存在,“options”和“default”會(huì)被合并,構(gòu)建成一個(gè)新的settings,然后用“data()”保存在元素中。
2025國考·省考課程試聽報(bào)名