有時您但願在整個代碼中提供一些功能。例如,也許你想要一個單一的方法,你能夠調用一個jQuery選擇,對選擇執行一系列的操做。在這種狀況下,您可能須要編寫一個插件。javascript
連接jQuery如何工做101:jQuery對象方法
在咱們編寫本身的插件以前,首先要了解一下jQuery如何工做。看看這段代碼:css
1
|
$( "a" ).css( "color", "red" );
|
這是一些很基礎的jQuery代碼,但你知道幕後發生了什麼嗎?不管什麼時候使用該$
函數來選擇元素,它返回一個jQuery對象。這個對象包含了全部的你已經使用(方法.css()
,.click()
等)和全部適合你的選擇要素。jQuery對象從對象中獲取這些方法$.fn
。這個對象包含了全部的jQuery對象方法,若是咱們想編寫本身的方法,它也須要包含這些方法。html
連接基本插件創做
假設咱們要建立一個插件,使一組檢索到的元素中的文本變爲綠色。咱們所要作的就是添加一個調用的函數greenify
來$.fn
,這將是可用的,就像任何其餘的jQuery對象的方法。java
1
2
3
4
五
|
$.fn.greenify = function() {
this.css( "color", "green" );
};
$( "a" ).greenify(); // Makes all the links green.
|
注意使用.css()
,另外一種方法,咱們使用this
,而不是$( this )
。這是由於咱們的greenify
函數是與之相同的對象的一部分.css()
。node
連接連接
這是有效的,可是咱們須要作的幾件事情就是讓咱們的插件在現實生活中生存下去。當您將五個或六個操做連接到一個選擇器上時,jQuery的功能之一就是連接。這是經過使全部jQuery對象方法再次返回原始的jQuery對象來實現的(有一些例外:.width()
調用無參數返回所選元素的寬度,而且不可連接)。使咱們的插件方法可連接須要一行代碼:jquery
1
2
3
4
五
6
|
$.fn.greenify = function() {
this.css( "color", "green" );
return this;
}
$( "a" ).greenify().addClass( "greenified" );
|
連接保護$別名和添加範圍
該$
變量是JavaScript庫中很受歡迎,若是你正在使用jQuery的另外一個庫,你將不得不做出的jQuery不使用$
帶jQuery.noConflict()
。然而,這將會破壞咱們的插件,由於它是用函數$
的別名假設來寫的jQuery
。爲了與其餘插件很好地工做,而且仍然使用jQuery的$
別名,咱們須要把咱們全部的代碼裏面當即調用函數表達式,而後傳遞給函數jQuery
,並命名參數$
:express
1
2
3
4
五
6
7
8
|
(function ( $ ) {
$.fn.greenify = function() {
this.css( "color", "green" );
return this;
};
}( jQuery ));
|
此外,當即調用函數的主要目的是容許咱們擁有本身的私有變量。僞裝咱們想要一個不一樣的顏色綠色,咱們想把它存儲在變量中。promise
1
2
3
4
五
6
7
8
9
10
|
(function ( $ ) {
var shade = "#556b2f";
$.fn.greenify = function() {
this.css( "color", shade );
return this;
};
}( jQuery ));
|
連接最小化插件足跡
編寫插件時只需佔用一個插槽就是很好的作法$.fn
。這樣能夠減小您的插件被覆蓋的機會,而且插件將覆蓋其餘插件的機會。換句話說,這是壞的:閉包
1
2
3
4
五
6
7
8
9
10
11
|
(function( $ ) {
$.fn.openPopup = function() {
// Open popup code.
};
$.fn.closePopup = function() {
// Close popup code.
};
}( jQuery ));
|
有一個插槽會更好,並使用參數來控制一個插槽執行什麼操做。app
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
|
(function( $ ) {
$.fn.popup = function( action ) {
if ( action === "open") {
// Open popup code.
}
if ( action === "close" ) {
// Close popup code.
}
};
}( jQuery ));
|
連接使用each()
方法
您的典型jQuery對象將包含對任意數量的DOM元素的引用,這就是爲何jQuery對象一般被稱爲集合。若是要對特定元素進行任何操做(例如獲取數據屬性,計算特定位置),則須要使用.each()
循環元素。
1
2
3
4
五
6
7
|
$.fn.myNewPlugin = function() {
return this.each(function() {
// Do something to each element here.
});
};
|
請注意,咱們返回結果.each()
而不是返回this
。既然.each()
已是可連接的,它返回this
,而後咱們返回。這是保持可連接性的一種更好的方法,而不是咱們迄今爲止所作的一切。
連接接受選項
隨着您的插件愈來愈複雜,最好經過接受選項來使您的插件可自定義。最簡單的方法是這樣作,特別是若是有不少選項,那就是一個對象文字。咱們更改咱們的greenify插件以接受一些選項。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
(function ( $ ) {
$.fn.greenify = function( options ) {
// This is the easiest way to have default options.
var settings = $.extend({
// These are the defaults.
color: "#556b2f",
backgroundColor: "white"
}, options );
// Greenify the collection based on the settings variable.
return this.css({
color: settings.color,
backgroundColor: settings.backgroundColor
});
};
}( jQuery ));
|
使用示例
1
2
3
|
$( "div" ).greenify({
color: "orange"
});
|
爲默認值color
的#556b2f
獲取經過重寫$.extend()
爲橙色。
連接在一塊兒
這是一個使用咱們討論的一些技巧的小插件的例子:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
|
(function( $ ) {
$.fn.showLinkLocation = function() {
this.filter( "a" ).each(function() {
var link = $( this );
link.append( " (" + link.attr( "href" ) + ")" );
});
return this;
};
}( jQuery ));
// Usage example:
$( "a" ).showLinkLocation();
|
這個方便的插件遍歷集合中的全部錨點,並將該href
屬性附加在括號中。
1
2
3
4
五
|
<!-- Before plugin is called: -->
<a href="page.html">Foo</a>
<!-- After plugin is called: -->
<a href="page.html">Foo (page.html)</a>
|
咱們的插件能夠經過如下優化:
1
2
3
4
五
6
7
8
9
10
11
12
13
|
(function( $ ) {
$.fn.showLinkLocation = function() {
this.filter( "a" ).append(function() {
return " (" + this.href + ")";
});
return this;
};
}( jQuery ));
|
咱們使用該.append()
方法的能力來接受回調,而且該回調的返回值將決定附加到集合中每一個元素的內容。還要注意,咱們沒有使用該.attr()
方法來檢索href
屬性,由於本機DOM API能夠方便地訪問aptly named href
屬性。
高級插件概念
連接提供公共訪問默認插件設置
咱們能夠並且應該對上面的代碼進行改進是公開默認的插件設置。這很重要,由於它使插件用戶很容易用最小的代碼覆蓋/定製插件。這就是咱們開始利用函數對象的地方。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
|
// Plugin definition.
$.fn.hilight = function( options ) {
// Extend our default options with those provided.
// Note that the first argument to extend is an empty
// object – this is to keep from overriding our "defaults" object.
var opts = $.extend( {}, $.fn.hilight.defaults, options );
// Our plugin implementation code goes here.
};
// Plugin defaults – added as a property on our plugin function.
$.fn.hilight.defaults = {
foreground: "red",
background: "yellow"
};
|
如今,用戶能夠在腳本中包含這樣一行:
1
2
3
|
// This needs only be called once and does not
// have to be called from within a "ready" block
$.fn.hilight.defaults.foreground = "blue";
|
如今咱們能夠像這樣調用插件方法,它將使用藍色的前景色:
1
|
$( "#myDiv" ).hilight();
|
您能夠看到,咱們容許用戶編寫一行代碼來更改插件的默認前景色。而用戶仍然能夠選擇性地覆蓋這個新的默認值:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
|
// Override plugin default foreground color.
$.fn.hilight.defaults.foreground = "blue";
// ...
// Invoke plugin using new defaults.
$( ".hilightDiv" ).hilight();
// ...
// Override default by passing options to plugin method.
$( "#green" ).hilight({
foreground: "green"
});
|
連接提供公共訪問次要功能做爲適用
該項目與上一個項目緊密相關,是擴展插件(並容許其餘擴展插件)的有趣方式。例如,咱們的插件的實現能夠定義一個稱爲「格式」的功能,其格式化高亮文本。咱們的插件可能看起來像這樣,默認實現的格式方法定義在hilight函數下面:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// Plugin definition.
$.fn.hilight = function( options ) {
// Iterate and reformat each matched element.
return this.each(function() {
var elem = $( this );
// ...
var markup = elem.html();
// Call our format function.
markup = $.fn.hilight.format( markup );
elem.html( markup );
});
};
// Define our format function.
$.fn.hilight.format = function( txt ) {
return "<strong>" + txt + "</strong>";
};
|
咱們能夠輕鬆地支持選項對象上的另外一個屬性,容許提供回調函數來覆蓋默認格式。這是支持自定義插件的另外一個很好的方法。這裏顯示的技術經過實際暴露格式功能使其能夠從新定義,從而進一步擴展。使用這種技術,其餘人可能會發布本身的插件自定義覆蓋 - 換句話說,這意味着其餘人能夠爲插件編寫插件。
考慮到咱們在本文中構建的簡單的示例插件,您可能會想知道什麼時候會有用。一個現實世界的例子是循環插件。循環插件是一個幻燈片插件,它支持多種內置的轉換效果 - 滾動,滑動,淡入淡出等。可是,實際上,沒有辦法定義可能但願應用於幻燈片轉換的每種類型的效果。這就是這種類型的可擴展性是有用的。循環插件公開了一個「transition」對象,用戶能夠添加本身的自定義轉換定義。它在插件中定義以下:
1
2
3
4
五
|
$.fn.cycle.transitions = {
// ...
};
|
這種技術使得其餘人能夠定義和提供插件到循環插件的轉換定義。
連接保持私人功能私有
暴露部分插件被覆蓋的技術能夠很是強大。可是,您須要仔細考慮實施的哪些部分才能公開。一旦暴露,您須要記住,調用參數或語義的任何更改可能會破壞向後兼容性。做爲通常規則,若是您不肯定是否公開特定功能,那麼您可能不該該。
那麼,咱們如何定義更多的功能,而不會混淆命名空間,而不會暴露實現?這是關閉的工做。爲了演示,咱們將在咱們的插件中添加另外一個函數叫作「debug」。調試功能將所選元素的數量記錄到控制檯。要建立一個閉包,咱們將整個插件定義包裝在一個函數中(如jQuery創做指南中所詳述的)。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// Create closure.
(function( $ ) {
// Plugin definition.
$.fn.hilight = function( options ) {
debug( this );
// ...
};
// Private function for debugging.
function debug( obj ) {
if ( window.console && window.console.log ) {
window.console.log( "hilight selection count: " + obj.length );
}
};
// ...
// End of closure.
})( jQuery );
|
咱們的「調試」方法沒法從關閉的外部訪問,所以對咱們的實現是私有的。
連接Bob和Sue
假設鮑勃建立了一個邪惡的新畫廊插件(稱爲「超級畫廊」),其中包含一幅圖像列表,並使其可導航。鮑勃拋出了一些動畫,使它更有趣。他試圖使插件儘量的自定義,最終結果是這樣的:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
|
jQuery.fn.superGallery = function( options ) {
// Bob's default settings:
var defaults = {
textColor: "#000",
backgroundColor: "#fff",
fontSize: "1em",
delay: "quite long",
getTextFromTitle: true,
getTextFromRel: false,
getTextFromAlt: false,
animateWidth: true,
animateOpacity: true,
animateHeight: true,
animationDuration: 500,
clickImgToGoToNext: true,
clickImgToGoToLast: false,
nextButtonText: "next",
previousButtonText: "previous",
nextButtonTextColor: "red",
previousButtonTextColor: "red"
};
var settings = $.extend( {}, defaults, options );
return this.each(function() {
// Plugin code would go here...
});
};
|
第一件事多是你的頭腦(好的,也許不是第一個),這個插件必須有多大,以適應這樣一個級別的定製。插件,若是不是虛構的,可能會比必要的大得多。只有這麼多千字節的人會願意花費!
如今,咱們的朋友鮑勃認爲這一切都很好; 事實上,他對插件及其定製級別印象深入。他認爲,全部的選擇均可以使用更爲通用的解決方案,能夠在許多不一樣的狀況下使用。
蘇,咱們的另外一個朋友,決定使用這個新的插件。她已經設置了全部的選項,如今有一個工做的方案坐在她面前。只有五分鐘後,玩插件後,若是每張圖片的寬度都以較慢的速度動畫,她意識到畫廊會更好看。她匆忙搜索Bob的文檔,但沒有發現animateWidthDuration選項!
連接你看到問題嗎?
這不是真的關於您的插件有多少選項; 但它有什麼選擇!
鮑勃已經超過了頂峯。他提供的定製水平雖然可能高,但實際上至關低,特別是考慮到使用此插件時可能須要控制的全部可能的事情。鮑勃犯了錯誤提供了不少好笑的具體選擇,使他的插件更難以自定義!
連接更好的模型
因此很明顯:鮑勃須要一個新的定製模式,一個不放棄控制或抽出必要細節的模型。
鮑勃如此吸引這個高級簡單性的緣由是,jQuery框架很是適合於這種觀念。提供一個previousButtonTextColor選項是好的和簡單的,但讓咱們面對它,絕大多數的插件用戶將要更多的控制!
如下是一些提示,可幫助您爲插件建立更好的可定製選項:
連接不要建立特定於插件的語法
使用您的插件的開發人員不該該學習一種新的語言或術語,只是爲了完成這項工做。
鮑勃認爲他提供最大的定製與他的延遲選項(看上面)。他作的這樣,他的插件能夠指定四個不一樣的延遲,「很短」,「很短」,「至關長」或「很長」:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var delayDuration = 0;
switch ( settings.delay ) {
case "very short":
delayDuration = 100;
break;
case "quite short":
delayDuration = 200;
break;
case "quite long":
delayDuration = 300;
break;
case "very long":
delayDuration = 400;
break;
default:
delayDuration = 200;
}
|
這不只限制了人們的控制水平,並且佔用了至關多的空間。十二行代碼只是爲了定義延遲時間有點多,你不以爲嗎?構建此選項的更好方法是讓插件用戶以數字的形式指定時間(以毫秒爲單位),以便不須要處理該選項。
這裏的關鍵不在於經過抽象來減小控制的程度。您的抽象,不管如何,能夠像您想要的同樣簡單,但確保使用您的插件的人仍然會有這麼多追捧的低級控制!(低級個人意思是非抽象的)
連接充分控制元素
若是您的插件建立要在DOM中使用的元素,那麼爲插件用戶提供一些訪問這些元素的方法是個好主意。有時這意味着給予某些元素ID或類。但請注意,您的插件不該該在內部依賴這些鉤子:
執行不良
1
2
3
4
|
// Plugin code
$( "<div class='gallery-wrapper' />" ).appendTo( "body" );
$( ".gallery-wrapper" ).append( "..." );
|
爲了容許用戶訪問甚至操縱該信息,您能夠將其存儲在包含插件設置的變量中。之前的代碼的更好的實現以下所示:
1
2
3
4
五
6
7
|
// Retain an internal reference:
var wrapper = $( "<div />" )
.attr( settings.wrapperAttrs )
.appendTo( settings.container );
// Easy to reference later...
wrapper.append( "..." );
|
請注意,咱們建立了一個引用包裝器的引用,而且咱們還調用該.attr()
方法來向元素添加任何指定的屬性。因此在咱們的設置中可能會像這樣處理:
1
2
3
4
五
6
7
8
9
10
|
var defaults = {
wrapperAttrs : {
class: "gallery-wrapper"
},
// ... rest of settings ...
};
// We can use the extend method to merge options/settings as usual:
// But with the added first parameter of TRUE to signify a DEEP COPY:
var settings = $.extend( true, {}, defaults, options );
|
在$ .extend()方法如今將經過全部嵌套對象遞歸給咱們設定值和經過選擇二者的合併版本,給人傳遞的選項優先。
插件用戶如今有權力指定該包裝器元素的任何屬性,所以若是它們須要任何CSS樣式的鉤子,那麼他們能夠很容易地添加一個類或更改ID的名稱,而無需去挖掘插件源碼。
可使用相同的模型來讓用戶定義CSS樣式:
1
2
3
4
五
6
7
8
9
10
|
var defaults = {
wrapperCSS: {},
// ... rest of settings ...
};
// Later on in the plugin where we define the wrapper:
var wrapper = $( "<div />" )
.attr( settings.wrapperAttrs )
.css( settings.wrapperCSS ) // ** Set CSS!
.appendTo( settings.container );
|
您的插件可能有一個關聯的樣式表,開發人員能夠添加CSS樣式。即便在這種狀況下,最好提供一些方便的方法來設置JavaScript中的樣式,而無需使用選擇器來獲取元素。
連接提供回調功能
什麼是回調? - 回調本質上是一個稍後調用的函數,一般由一個事件觸發。它做爲參數傳遞,一般是組件的發起調用,在這種狀況下是一個jQuery插件。
若是您的插件由事件驅動,那麼爲每一個事件提供回調功能多是一個好主意。此外,您能夠建立本身的自定義事件,而後爲其提供回調。在這個gallery插件中添加一個「onImageShow」回調多是有意義的。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var defaults = {
// We define an empty anonymous function so that
// we don't need to check its existence before calling it.
onImageShow : function() {},
// ... rest of settings ...
};
// Later on in the plugin:
nextButton.on( "click", showNextImage );
function showNextImage() {
// Returns reference to the next image node
var image = getNextImage();
// Stuff to show the image here...
// Here's the callback:
settings.onImageShow.call( image );
}
|
而不是經過傳統方式(加括號)啓動回調,咱們在上下文中調用它將image
是對圖像節點的引用。這意味着您能夠經過this
回調中的關鍵字訪問實際的圖像節點:
1
2
3
4
五
6
7
|
$( "ul.imgs li" ).superGallery({
onImageShow: function() {
$( this ).after( "<span>" + $( this ).attr( "longdesc" ) + "</span>" );
},
// ... other options ...
});
|
一樣,您能夠添加一個「onImageHide」回調函數和其餘許多回調函數。回調點是給插件用戶一個簡單的方法來添加額外的功能,而不須要在源代碼中挖掘。
連接記住,這是一個妥協
您的插件沒法在每種狀況下工做。一樣地,若是您提供的控制方法不是不多或不多,則不會很是有用。因此,請記住,這將是一個妥協。您必須始終考慮的三件事情是:
- 靈活性:您的插件能夠處理多少狀況?
- 大小:插件的大小是否對應於其功能級別?也許你使用一個很是基本的工具提示插件,若是它是20k的大小?- 可能不會!
- 性能:您的插件是否以任何方式大量處理選項?這是否影響速度?爲最終用戶帶來的開銷是否值得?