本書代碼參考: Learning jQuery Code Listing Browser
原書: jQuery基礎教程
目錄:
1. 使用$()函數
$()函數其實是創建了一個jQuery對象. 這個函數接受CSS選擇符作為參數,充當一個工廠, 返回包含頁面中對應元素的jQuery對象. 所有能在樣式表中使用的選擇符都可以傳給這個函數, 隨后就可以對匹配的元素集合應用jQuery方法.
在jQuery中,美元符號$其實就是標示符jQuery的"別名".
2. 選擇符
1. 基本選擇符 $('p') //取得所有標簽為p的元素 $('.class') //取得所有類為class的元素 $('#id') //取得id為id的元素 //以及其他css中的選擇符 2. 屬性選擇符 $('img[alt]') //選擇帶有alt屬性的所有圖像元素 //^表示值在字符串的開始, $表示值在字符串的結尾. *表示要匹配的值可以出現在字符串的任意位置, !表示對值取反 $('a[href^="mailto:"]') //選擇頁面中所有mailto:鏈接 $('a[href$=".pdf"]') //選擇頁面中所有pdf文檔鏈接 $('a[href^="http"][href*="henry"]') //選擇href屬性以http開頭且在任意位置包含henry的元素 3. 自定義選擇符 $('div.horizontal:eq(1)') //取得集合中的第二個元素 $('tr:even') //選擇奇數行. 之所以是奇數行是因為第一行的編號是0 還可以寫作: $('tr').filter(':even')
$('tr:nth-child(odd)') //選擇奇數行. :nth-child()是jq中唯一從1開始計數的選擇符 $('tr:contains(Henry)') //根據上下文內容選擇元素. contains選擇符區分大小寫 4. 基於表單的選擇符 $('input[type="radio"]:checked') //可以選擇所有選中的單選按鈕 $('input[type="text"]:disabled') //選擇禁用的文本輸入字段
更多的基於表單的選擇符
:input
:button
:enabled
:disabled
:checked:selected
3. DOM遍歷方法
filter方法
$('tr').filter(':even') $('a').filter(function(){ return this.hostname && this.hostname!=location.hostname; }) //選擇包含帶有域名的href屬性的,且域名不等於頁面當前所在域的名稱的a元素
.next() //選擇下一個最接近的同輩元素 $('td:contains(Henry)').next() .nextAll() //選擇后面的全部同輩元素 //對應方法 .prev() .prevAll() .siblings() //選擇處於相同DOM層次的所有其他元素,無論這些元素處於當前元素之前還是之后 .parent().children() //另一種方法
連綴方法的原理:
幾乎所有的jQuery方法都會返回一個jQuery對象, 因而可連綴調用多個jQuery方法.
4. 訪問DOM元素
.get() var myTag = $('#my-element').get(0).tagName; //獲取帶有id為my-element屬性的元素的標簽名 var myTag = $('my-element')[0].tagName; //一種簡寫方式
函數 | 描述 |
---|---|
.add() | 將元素添加到匹配元素的集合中。 |
.andSelf() | 把堆棧中之前的元素集添加到當前集合中。 |
.children() | 獲得匹配元素集合中每個元素的所有子元素。 |
.closest() | 從元素本身開始,逐級向上級元素匹配,並返回最先匹配的祖先元素。 |
.contents() | 獲得匹配元素集合中每個元素的子元素,包括文本和注釋節點。 |
.each() | 對 jQuery 對象進行迭代,為每個匹配元素執行函數。 |
.end() | 結束當前鏈中最近的一次篩選操作,並將匹配元素集合返回到前一次的狀態。 |
.eq() | 將匹配元素集合縮減為位於指定索引的新元素。 |
.filter() | 將匹配元素集合縮減為匹配選擇器或匹配函數返回值的新元素。 |
.find() | 獲得當前匹配元素集合中每個元素的后代,由選擇器進行篩選。 |
.first() | 將匹配元素集合縮減為集合中的第一個元素。 |
.has() | 將匹配元素集合縮減為包含特定元素的后代的集合。 |
.is() | 根據選擇器檢查當前匹配元素集合,如果存在至少一個匹配元素,則返回 true。 |
.last() | 將匹配元素集合縮減為集合中的最后一個元素。 |
.map() | 把當前匹配集合中的每個元素傳遞給函數,產生包含返回值的新 jQuery 對象。 |
.next() | 獲得匹配元素集合中每個元素緊鄰的同輩元素。 |
.nextAll() | 獲得匹配元素集合中每個元素之后的所有同輩元素,由選擇器進行篩選(可選)。 |
.nextUntil() | 獲得每個元素之后所有的同輩元素,直到遇到匹配選擇器的元素為止。 |
.not() | 從匹配元素集合中刪除元素。 |
.offsetParent() | 獲得用於定位的第一個父元素。 |
.parent() | 獲得當前匹配元素集合中每個元素的父元素,由選擇器篩選(可選)。 |
.parents() | 獲得當前匹配元素集合中每個元素的祖先元素,由選擇器篩選(可選)。 |
.parentsUntil() | 獲得當前匹配元素集合中每個元素的祖先元素,直到遇到匹配選擇器的元素為止。 |
.prev() | 獲得匹配元素集合中每個元素緊鄰的前一個同輩元素,由選擇器篩選(可選)。 |
.prevAll() | 獲得匹配元素集合中每個元素之前的所有同輩元素,由選擇器進行篩選(可選)。 |
.prevUntil() | 獲得每個元素之前所有的同輩元素,直到遇到匹配選擇器的元素為止。 |
.siblings() | 獲得匹配元素集合中所有元素的同輩元素,由選擇器篩選(可選)。 |
.slice() | 將匹配元素集合縮減為指定范圍的子集。 |
參考: jQuery 參考手冊 - 遍歷
第3章 事件
1. $(document).ready()和window.onload事件
- $(document).ready()注冊是事件處理程序會在DOM完全就緒並可以使用時調用.這意味着所有元素對腳本而言都是可以訪問的,但是並不意味着所有關聯的文件都已經下載完畢. 即當HTML下載完成並解析成DOM樹后,代碼就可以運行.
- 而window.onload意味着文檔完全下載到瀏覽器中了.
- 一般來說,使用$(document).ready()要優於onload. 但是因為支持文件可能還沒有加載完成,所以類似圖像的高度和寬度這樣的屬性不一定有效. 如果需要訪問這些屬性, 可能需要選擇實現一個onload事件處理程序, 使這兩種機制能夠和平共存.
$(document).ready()可簡寫為: $(); 即:
$(function(){ //code here });
為了避免$的沖突,可以設置出讓$標示符控制權
jQuery.noConflict(); //之和就通過jQuery來調用jQuery的方法
這種情況下,可以在.ready()函數中使用$的技巧----傳遞給ready函數的回調函數可以接收一個參數----jQuery對象本身,利用這個參數,可以重新命名jQuery為$,而不必擔心造成沖突
jQuery(function($){ //使用$的代碼 });
2. 事件添加
//.on綁定事件 $(this).on('click',function(){}); //.click $(this).click(function(){}) //添加類 $('btn').addClass('classname'); //移除類 $('btn').removeClass('classname'); //根據相應的類是否存在而添加或刪除類 $('btn').toggleClass('classname');
.hover()方法: 接受兩個函數參數,第一個會在鼠標指針進入被選擇的元素時執行,而第二個函數會在鼠標指針離開該元素時觸法
//設定事件目標 $('#switcher').click(function(event){ if(event.target = this){ $('#switcher button').toggleClass('hidden'); } }); //停止事件傳播 event.stopPropagation(); //避免其他DOM元素響應事件 //阻止默認操作 .preventDefault();
同時調用.stopPropagation()和preventDefault()的一種簡寫方式: 在事件處理程序中返回false
3. 事件委托:
$(function(){ $('#switcher').click(function(event){ if($(event.target).is('button')){ //.is()方法接收一個選擇符表達式,然后用選擇符來測試當前jQuery對象. 如果集合中至少有一個元素與選擇符匹配,.is()返回true var bodyClass = event.target.id.split('-')[1]; $('body').removeClass().addClass(bodyClass); $('#switcher button').removeClass('selected'); $(event.target).addClass('selected'); //這里this引用的都是"#switch"對象,因此要訪問被單擊的按鈕斗毆要通過event.target來引用 event.stopPropagation(); } }); });
.on()方法也可以接收相應參數實現事件委托. 如果給.on()方法傳入的第二個參數是一個選擇符表達式,jQuery會把click事件處理程序綁定到#switch對象,同時比較event.target和選擇符表達式. 如果匹配, jQuery會把this關鍵字映射到匹配的元素, 否則不會執行事件處理程序.
4. 移除事件處理
.off('click')
還可以為事件處理程序添加命名空間
$('#switcher').on('click.collapse', function(){}); $('#switcher').off('click.collapse');
對於事件處理系統而言, 后綴.collapse是不可見的.
如果不使用匿名函數,則就可以不使用事件命名空間, .off()會把命名函數作為第二個參數, 結果只會解除對特定處理程序的棒的.
第4章 樣式與動畫
1. 通過.css()來獲取或修改元素樣式屬性的值.
為了獲取某個樣式屬性的值, 可以為這個方法傳遞一個字符串形式的屬性名, 然后同樣得到一個字符串形式的屬性值. 要取得多個樣式屬性的值, 可以傳入屬性名的數組, 得到的則是屬性和值勾搭的對象.
對於由多個單詞構成的屬性名, jQuery既可以解釋連字符版的css表示法(background-color), 也可以解釋駝峰大小寫形式的DOM表示法(backgroundColor)
對於帶瀏覽器前綴的屬性值(比如-webkit-property-name, -ms-property-name), 用jQuery不需要提前檢測, 而可以直接使用標准的屬性名. 比如:
.css('propertyName','value'). //如果樣式對象中不存在這個屬性,jQuery就會依次檢測所有帶前綴(Webkit, o, Moz, ms)的屬性, 然后使用第一個找到的那個屬性.
2. 隱藏和顯示元素
.hide() .show()
- .hide()方法會把匹配的元素集合的內聯style屬性設置為display:none. 它可以在把display的值變為none之前, 記住原先的display值
- .show()方法會將匹配的元素集合的display屬性,恢復為應用display:none之前的可見屬性
可以為hide()和show()方法中指定時長參數, 產生動畫效果.比如
.hide('duration')方法會同時減少元素的高度, 寬度和不透明度, 直到三個屬性的值都為0. 與此同時會為該元素應用display:none. 而show('duration')方法相反.
此外,還可以指定兩種預設的速度參數 'slow' 和 'fast' ,分別在600ms和200ms內完成效果. 如果傳入的是其他字符串,jQuery就會在默認的400ms內完成效果.要指定更精確的速度,可以使用毫秒數值. 數值不需要用引號.
一些其他顯示/隱藏元素的函數:
//參數: 1. speed(毫秒/'slow'/'normal'/'fast') // 2. callback[執行完的回調參數] //淡入淡出 只是改變不可見性 .fadeIn(); //逐漸增大不透明度, 淡入 .fadeOut(); //逐漸減少不透明度, 淡出 //滑上和滑下 .slideDown() .slideUp() //切換可見性 .slideToggle();
3. 創建自定義動畫
.animate():兩種形式
- 第一種形式. 接收四個參數
- 一個包含樣式屬性及值的對象: 與前面討論的.css()方法中的參數類似
- 可選的時長參數: 既可以是預置的字符串, 也可以是毫秒數值
- 可選的緩動(easing)類型
- 可選的回調函數
.animate({property1: 'value1', property2: 'value2'}, duration, easing, function(){ //... });
- 第二種形式. 接收兩個參數
- 一個屬性對象
- 一個選項對象.
- 其實就是把第一種形式的第2-4個參數封裝在了另一個對象中, 同時又添加了兩個選項
.animate({ property1:'value1', property2:'value2' }, { duration:'value', easing: 'value', specialEasing: { property1: 'easing1', property2: 'easing2' }, complete: function(){ //... }, queue: true, step: callback });
當使用 animate() 時,必須使用 Camel 標記法書寫所有的屬性名,比如,必須使用 paddingLeft 而不是 padding-left,使用 marginRight 而不是 margin-right,等等。
還可以用.animate()為同一組元素應用多重效果, 可以通過連綴這些效果來實現排隊
簡單概括:
- 一組元素上的效果
- 當在一個.animate()方法中以多個屬性的方法應用時, 是同時發生的
- 當以方法連綴的形式應用時, 是按順序發生的(排隊效果)除非queue選項值為false
- 多組元素上的效果
- 默認情況下是同時發生的
- 當在另一個效果方法或者在.queue()方法的回調函數中應用時, 是按順序發生的(排隊效果)
第5章 操作DOM
1. 操作屬性
1) .attr() .removeAttr()
//操作非類屬性 .attr(); //和.css()方法類似, 可以接收一對屬性名/屬性值參數 或者是一個包含鍵值對的對象 .removeAttr(); //還可以使用值回調為每個元素設置不同的屬性值 $(document).ready(function() { // Use attr() to add an id, rel, and title. $('div.chapter a[href*="wikipedia"]').attr({ rel: 'external', title: function(){
return 'Learn more about ' + $(this).text() //利用值回調的上下文
+ ' at Wikipedia.';
}, id: function(index, oldValue) { //值回調 return 'wikilink-' + index; } }); });
每次觸發值回調, 都會給它傳入兩個參數. 第一個參數是整數, 表示迭代次數. 第二個參數保存的是修改之前的屬性的值, 這里並沒有用到.
2) DOM元素屬性
HTML屬性和DOM屬性:
- HTML屬性是指頁面標記中放在引號中的值
- DOM屬性則是指通過JavaScript能夠存取的值
在jQuery中, 可以通過.prop()方法取得和設置DOM屬性:
//取得"checked"屬性的當前值 var currentChecked = $('.my-checkbox').prop('checked'); //設置"checked"屬性的值 $('.my-checkbox').prop('checked', false);
3) 表單控件的值
HTML屬性和DOM屬性差別最大就數表單控件的值. 比如,文本輸入框的value屬性在DOM中的屬性叫defaultValue, DOM中就沒有value值. 而選項列表(select)元素, 其選項的值在DOM中通常是通過selectedIndex屬性, 或者通過其選項元素的selected屬性來取得.
由於存在這些差異, 在取得和設置表達控件值時,最好不要用.attr()方法. 而對於選項列表, 最好也不要用.prop()方法. 建議用jQuery提供的val()方法:
//取得文本輸入框的當前值 var inputValue = $('#my-input').val(); //取得選項列表的當前值 var selectValue = $('#my-select').val(); //設置單選列表的值 $('#my-single-select').val('value3'); //設置多選列表的值 $('#my-multi-select').val(['value1','value2']);
2. DOM樹操作
-
$() 可以創建新元素
-
插入新元素
- .insertBefore(): 在現有元素外部, 之前添加內容
- .prependTo(): 在現有元素內部, 之前添加內容
- .appendTo(): 在現有元素內部, 之后添加內容
- .insertAfter(): 在現有元素外部, 之后添加內容
// Add "back to top" links. $('<a href="#top">back to top</a>').insertAfter('div.chapter p'); $('<a id="top"></a>').prependTo('body');
-
移動元素
// Create footnotes. $('span.footnote').insertBefore('#footer');
-
包裝元素
// Create footnotes. $('span.footnote') .insertBefore('#footer') .wrapAll('<ol id="notes"></ol>') //把所有腳本都包含在一個<ol>中 .wrap('<li></li>') //把每一個腳注分布包裝在自己的<li>中
用.each()方法作為顯式迭代器, 為提取腳注的位置加標記和編碼
-
// Create footnotes.
-
var $notes = $('<ol id="notes"></ol>').insertBefore('#footer');
-
$ ('span.footnote').each(function(index) {
-
$ ('<sup>' + (index + 1) + '</sup>').insertBefore(this);
-
$ (this).appendTo($notes).wrap('<li></li>');
-
});
-
-
使用反向插入方法
對應的反向方法:
$('<p>Hello</p>').appendTo('#container'); //與下面的代碼結果一樣 $('#container').append('<p>Hello</p>');
所以上面的代碼還可以寫作:
// Create footnotes. var $notes = $('<ol id="notes"></ol>').insertBefore('#footer'); $('span.footnote').each(function(index) { $(this) .before('<sup>' + (index + 1) + '</sup>') .appendTo($notes) .wrap('<li></li>'); });
-
復制元素
.clone()
$('div.chapter p:eq(0').clone().insertBefore('div.chapter');
-
元素內容
- .html() //返回匹配元素的html標記, 或替換元素內容(如果有參數)
- .text() //也可以取得匹配元素的內容,或者用新字符替換匹配元素的內容. 但是.text()始終會取得或設置純文本內容. 所有html標簽都將被忽略掉, 而所有html實體也會被轉化為對應的字符
-
DOM操作方法的簡單總結
//要在html中創建新元素, 使用$()標簽 //要在每個匹配的元素中插入新元素: .append() .appendTo() .prepend() .prependTo() //要在每個匹配的元素相鄰的位置上插入新元素 .after() .insertAfter() .before() .insertBefore() //要在每個匹配的元素外部插入新元素 .wrap() .wrapAll() .wrapInner() //要用新元素或文本替換每個匹配的元素 .html() .text() .replaceAll() .replaceWith() //要移除每個匹配的元素中的元素 .empty() //要從文檔中移除每個匹配的元素及其后代元素, 但不實際刪除它們 .remove() .detach()
第6章 通過Ajax發送數據
Ajax實質: 就是一種無需刷新頁面即可從服務器(或客戶端)上加載數據的手段
$(document).ready(function() { $('#letter-a a').click(function(event) { event.preventDefault(); $.ajaxSetup({ //$.ajaxSetup()函數可以修改調用Ajax方法時每個選項的默認值. //這個函數與$.ajax()接受相同的選項對象參數. 之后所有Ajax請求都使用傳遞給該函數的選項, 除非明確覆蓋 url: 'a.html', type: 'POST', dataType: 'html' }); $.ajax({ type: 'GET', success: function(data) { $('#dictionary').html(data); } }); }); $('#letter-b a').click(function(event) { event.preventDefault(); $.getJSON('b.json', function(data) { //$.getJSON()方法會在取得相應文件后對文件進行處理. 在數據從服務器返回后, 它只是一個簡單的JSON格式的文本字符串. //$.getJSON()方法會解析這個字符串, 並將處理得到的JavaScript對象提供給調用代碼 //接受兩個參數, 第2個參數是當加載完成時調用的函數 //$.getJSON()函數: 沒有該方法適用的DOM元素;作為結果的對象只能提供給腳本,而不能插入到頁面中. //getJSON()是作為全局jQuery對象(由jQuery庫定義的jQuery或$對象)的方法定義的, 而不是個別jQuery對象實例的方法 //可把getJSON()方法看做類方法, 稱其為全局函數. 這些全局函數使用的是jQuery命名空間. var html = ''; $.each(data, function(entryIndex, entry) { //$.each()函數不操作jQuery對象, 它以數組或對象作為第一個參數,以回調函數作為第二個參數. //此外還需要將每次循環中數組或對象的當前索引和當前項作為回調函數的兩個參數 html += '<div class="entry">'; html += '<h3 class="term">' + entry.term + '</h3>'; html += '<div class="part">' + entry.part + '</div>'; html += '<div class="definition">'; html += entry.definition; if (entry.quote) { html += '<div class="quote">'; $.each(entry.quote, function(lineIndex, line) { html += '<div class="quote-line">' + line + '</div>'; }); if (entry.author) { html += '<div class="quote-author">' + entry.author + '</div>'; } html += '</div>'; } html += '</div>'; html += '</div>'; }); $('#dictionary').html(html); }); }); $('#letter-c a').click(function(event) { event.preventDefault(); $.getScript('c.js'); //$.getScript()也是一個全局函數. 接受一個url參數以查找腳本文件. //以這種方式取得的腳本會在全局環境下執行. 這意味着腳本有權訪問在全局環境中定義的函數和變量, 當然也包括jQuery自身 }); $('#letter-d a').click(function(event) { event.preventDefault(); $.get('d.xml', function(data) { //加載xml文檔. //通常,$.get()函數只是取得由url指定的文件, 然后將純文本格式的數據提供給回調函數. 但是在根據服務器提供的mine類型知道相應的是xml的情況下, 提供給回調函數的將是xml dom樹 $('#dictionary').empty(); $(data).find('entry').each(function() { var $entry = $(this); var html = '<div class="entry">'; html += '<h3 class="term">' + $entry.attr('term'); html += '</h3>'; html += '<div class="part">' + $entry.attr('part'); html += '</div>'; html += '<div class="definition">'; html += $entry.find('definition').text(); //jQuery內部的選擇符引擎, 對查找xml文檔元素也有效 var $quote = $entry.find('quote'); if ($quote.length) { html += '<div class="quote">'; $quote.find('line').each(function() { html += '<div class="quote-line">'; html += $(this).text() + '</div>'; }); if ($quote.attr('author')) { html += '<div class="quote-author">'; html += $quote.attr('author') + '</div>'; } html += '</div>'; } html += '</div>'; html += '</div>'; $('#dictionary').append($(html)); }); }); }); $('#letter-e a').click(function(event) { event.preventDefault(); //為了防止單擊這些鏈接時打開新的url. //當默認動作是重新加載頁面或重新打開新頁面時, 推薦使用preventDefault()而不是return false結束該處理程序. //return false意味着同時調用event.preventDefault()和event.stopPropagation(), 因此想要阻止事件冒泡, 還得調用后者 var requestData = {term: $(this).text()}; $.get('e.php', requestData, function(data) { //向服務器傳遞數據. 第二個參數是一個用來構建查詢關鍵字符串的鍵和值的對象. $('#dictionary').html(data); }).fail(function(jqXHR) { $('#dictionary') .html('Sorry, but an error occurred: ' + jqXHR.status) .append(jqXHR.responseText); }); }); $('#letter-f form').submit(function(event) { event.preventDefault(); var formValues = $(this).serialize(); $.get('f.php', formValues, function(data) { $('#dictionary').html(data); }); }); var url = 'http://examples.learningjquery.com/jsonp/g.php'; $('#letter-g a').click(function(event) { event.preventDefault(); $.getJSON(url + '?callback=?', function(data) { //使用JSONP加載遠程數據, 實現跨域
//可以通過使用 JSONP 形式的回調函數來加載其他網域的 JSON 數據,如 "url?callback=?"。jQuery 將自動替換 ? 為正確的函數名,以執行回調函數。 注意:此行以后的代碼將在這個回調函數執行前執行。
//該函數是簡寫的Ajax函數. 等價於: $.ajax({url:url, data: data, success: callback, dataType: json});
var html = ''; $.each(data, function(entryIndex, entry) { html += '<div class="entry">'; html += '<h3 class="term">' + entry.term + '</h3>'; html += '<div class="part">' + entry.part + '</div>'; html += '<div class="definition">'; html += entry.definition; if (entry.quote) { html += '<div class="quote">'; $.each(entry.quote, function(lineIndex, line) { html += '<div class="quote-line">' + line + '</div>'; }); if (entry.author) { html += '<div class="quote-author">' + entry.author + '</div>'; } html += '</div>'; } html += '</div>'; html += '</div>'; }); $('#dictionary').html(html); }); }); $('#letter-h a').click(function(event) { event.preventDefault(); $('#dictionary').load('h.html .entry'); }); var $loading = $('<div id="loading">Loading...</div>') .insertBefore('#dictionary'); //jQuery提供的一組觀察員函數, 來為各種與Ajax相關的事件注冊回調函數. //這些觀察員都是全局性的,且只能由$(document)調用 $(document).ajaxStart(function() { //當Ajax請求開始且尚未進行其他傳輸時出發.ajaxStart()的回調函數 $loading.show(); }).ajaxStop(function() { //最后一次活動請求終止時出發 $loading.hide(); }); //全局觀察員函數還有.ajaxError() $('body').on('click', 'h3.term', function() { $(this).siblings('.definition').slideToggle(); //事件委托 }); });
第7章 使用插件
第8章 開發插件
1. 在插件中使用$別名
為了防止jQuery的別名$已經被讓渡出去, 可以在插件的作用域內定義這個快捷方式, 使用立即調用的函數表達式(IIFE, Immediately Invoked Function Expression)
(function($){ //code here })(jQuery);
2. 添加新的全局函數
所謂全局函數, 實際上就是jQuery對象的方法, 但從實踐的角度上看,它們是位於jQuery命名空間內部的函數. 比如$.ajax(), $.each(), $.map().
給jQuery添加新的全局方法就是只要在第一節中的IIFE內部定義一個方法, 在函數外部就可以調用了. 因為它已經是jQuery的對象方法了. 比如:
/****************************************************************************** Our plugin code comes first in this document. Normally, plugins would appear in separate files named jquery.plugin-name.js, but for our examples it's convenient to place this plugin code in the same JavaScript file as the code that calls it. ******************************************************************************/ /****************************************************************************** $.sum() Return the total of the numeric values in an array/object. ******************************************************************************/ (function($) { $.sum = function(array) { var total = 0; $.each(array, function(index, value) { value = $.trim(value); value = parseFloat(value) || 0; total += value; }); return total; }; $.average = function(array) { if ($.isArray(array)) { return $.sum(array) / array.length; } return ''; }; })(jQuery); /****************************************************************************** End plugin code; begin custom script code. ******************************************************************************/ $(document).ready(function() { var $inventory = $('#inventory tbody'); var quantities = $inventory.find('td:nth-child(2)') .map(function(index, qty) { return $(qty).text(); }).get(); var sum = $.sum(quantities); $('#sum').find('td:nth-child(2)').text(sum); });
此外, 還可以利用$.extend()函數通過另一種語法定義全局函數:
(function($) { $.extend({ sum: function(array) { var total = 0; $.each(array, function(index, value) { value = $.trim(value); value = parseFloat(value) || 0; total += value; }); return total; }, average: function(array) { if ($.isArray(array)) { return $.sum(array) / array.length; } return ''; } }); })(jQuery);
為了避免沖突, 可以把屬於一個插件的全局對象都封裝到一個對象中. 即使用命名空間隔離函數 .比如:
(function($) { $.mathUtils = { sum: function(array) { var total = 0; $.each(array, function(index, value) { value = $.trim(value); value = parseFloat(value) || 0; total += value; }); return total; }, average: function(array) { if ($.isArray(array)) { return $.mathUtils.sum(array) / array.length; } return ''; } }; })(jQuery);
調用時需要:
var sum = $.mathUtils.sum(quantities); var average = $.mathUtils.average(prices);
3. 添加jQuery對象方法
- 擴展jQuery.fn對象. jQuery.fn是jQuery.prototype的別名
- 因為jQuery的選擇符表達式可能會匹配零,一個或多個元素, 所有在設計插件時應該考慮隱式迭代
(function($) { $.fn.swapClass = function(class1, class2) { this.each(function() { var $element = $(this); if ($element.hasClass(class1)) { $element.removeClass(class1).addClass(class2); } else if ($element.hasClass(class2)) { $element.removeClass(class2).addClass(class1); } }); }; })(jQuery);
- 方法連綴. 因此要在所有插件方法中返回一個jQuery對象, 除非相應的方法明顯用於取得不同的信息. 返回的jQuery對象通常就是this所引用的對象. 如果使用.each()迭代遍歷this, 那么可以只返回迭代的結果. 比如:
(function($) { $.fn.swapClass = function(class1, class2) { return this.each(function() { var $element = $(this); if ($element.hasClass(class1)) { $element.removeClass(class1).addClass(class2); } else if ($element.hasClass(class2)) { $element.removeClass(class2).addClass(class1); } }); }; })(jQuery);
這樣, 就可以在插件方法上連綴內置方法了
4. 提供靈活的方法參數
- 合適的時候, 利用回調函數支持靈活地修改插件行為, 從而不必修改插件代碼
- 如果插件是為了實現用戶界面元素, 或者需要跟蹤元素的狀態, 使用jQuery UI部件工廠來創建
附錄A JavaScript閉包
1. 內部函數
所謂內部函數, 就是定義在另一個函數中的函數. 可以避免污染命名空間.
而JS中的內部函數可以逃脫定義它們的外部函數. 逃脫的方法有多種:
- 可以給內部函數指定給一個全局變量:
var globalVar; function outerFn() { console.log('Outer function'); function innerFn() { console.log('Inner function'); } globalVar = innerFn; } console.log('outerFn():'); outerFn(); console.log('globalVar():'); globalVar();
- 可以通過在父函數中返回值來獲得內部函數的引用:
function outerFn() { console.log('Outer function'); function innerFn() { console.log('Inner function'); } return innerFn; } console.log('var fnRef = outerFn():'); var fnRef = outerFn(); //Outer function console.log('fnRef():'); fnRef();//Inner function
當內部函數在定義它的作用域的外部被引出時, 就創建了該內部函數的一個閉包.
這種情況下, 我們稱既不是內部函數局部變量, 也不是其參數的變量為自由變量, 稱外部函數的調用環境為封閉閉包的環境. 從本質上將, 如果內部函數引用了位於外部函數中的變量, 相當於授權該變量能夠被延遲使用. 因此, 當外部函數引用完成后, 這些變量的內存不會被釋放, 因為閉包仍然需要使用它們.
2. 在jQuery中創建閉包
依然要注意以下這個問題:
$(document).ready(function($) { // Stuff to do as soon as the DOM is ready; for(var i=0; i<5; i++){ $('<div>Print ' + i + '</div>') .click(function(){ console.log(i); }).insertBefore('body'); } });
這里單擊其中一項並不會看到相應的編號輸出, 而是都會顯示數值5. 即即使在綁定程序時i的值每次都不一樣, 每個click處理程序最終引用的i都相同. 都等於單擊事件實際發生時i的最終值(5).
要解決這個問題, 方法有:
-
使用jQuery的$.each()函數來代替for循環:
$(document).ready(function() { // Stuff to do as soon as the DOM is ready; $.each([0,1,2,3,4], function(index, value) { $('<div>Print ' + value + '</div>') .click(function(){ console.log(value); }).insertBefore('body'); }); });
這是因為函數的參數類似於在函數中定義的變量, 所以每次循環的value值實際上都是不同的變量. 結果, 每個click處理程序都指向一個不同的value變量, 因而每次單擊輸出的值會與元素的標簽文本匹配.
-
在for循環內部, 可以定義並執行一個新函數: 即使用立即調用的函數表達式(IIFE)
$(document).ready(function() { for(var i=0; i<5; i++){ (function(value){ $('<div>Print '+ value + '</div>') .click(function(){ console.log(value); }).insertBefore('body'); })(i); } });
-
使用jQuery的事件系統, 利用.on()方法.
該方法接受一個對象參數, 該參數以event.data的形式傳入事件處理函數中
$(document).ready(function() { for(var i=0; i<5; i++){ $('<div>Print '+ i + '</div>') .on('click', {value: i}, function(event){ console.log(event.data.value); }).insertBefore('body'); } });
因為event是函數的參數, 每次調用處理程序時它都是一個獨立的實例, 而不是在所有調用中共享的一個值.
3. 應對內存泄露的風險
1) 避免意外的引用循環
閉包可能會導致在不經意間創建引用u型擬合. 比如:
function outerFn() { var outerVar = {}; function innerFn() { console.log(outerVar); } outerVar.fn = innerFn; return innerFn; };
這里innerFn()創建了一個引用outerVar的閉包, 而outerVar又引用了innerFn()
而更隱蔽的情形是:
function outerFn() { var outerVar = {}; function innerFn() { console.log('hello'); } outerVar.fn = innerFn; return innerFn; };
這里雖然innerFn()沒有引用outerVar. 但是仍然沒有斷開循環. 即使innerFn()不再引用outerVar, outerVar也仍然位於innerFn()的封閉環境中. 由於閉包的原因, 位於outerFn()中的所有變量都隱含地被innerFn()所引用. 因此, 閉包會使意外地創建這些引用循環變得容易.
2) 控制DOM與JavaScript的循環
以上這種情況通常容易處理, 因為js能夠檢測到這些情況並在它們孤立時將其清除.
舊版本IE中存在的一種難以處理的引用循環問題. 當一個循環中同時包含DOM元素和常規JS元素時, IE無法釋放任何一個對象----因為這兩類對象是由不同的內存管理程序負責管理的. 即除非關閉瀏覽器, 否則這種循環在IE中永遠得不到釋放. 比如:
$(document).ready(function() { var button = document.getElementById('button-1'); button.onclick = function() { console.log('hello'); return false; }; });
當指定單擊事件處理程序時, 就創建了一個在其封閉的環境中包含button變量的閉包. 而且, 現在的button也包含一個指向閉包(onclick屬性自身)的引用. 這樣, 就導致了在IE中即使離開當前頁面也不會釋放這個循環.
為了釋放內存, 就需要斷開循環引用, 例如在關閉窗口關刪除onclick屬性. 另外, 可以向如下重寫代碼來避免:
function hello() { console.log('hello'); return false; } $(document).ready(function() { var button = document.getElementById('button-1'); button.onclick = hello; });
這樣,因為hello函數不再包含button, 引用就成了單向的(從button到hello), 不存在的循環, 就不會造成內存泄露了.
3) 用jQuery化解引用循環
$(document).ready(function() { var $button = $('#button-1'); $button.click(function(event) { event.preventDefault(); console.log('hello'); }); });
即使此時仍然會創建一個閉包, 並且也會導致同前面一樣的循環, 但這里的代碼卻不會使IE發生內存泄露. 由於jQuery考慮到了內存泄露的潛在危害, 所以它會手動釋放自己指定的所有事件處理程序.
另一種避免泄露的工具-----使用.data()方法可以像使用擴展屬性一樣, 將信息附加到DOM元素. 由於這里的數據並非直接保存在擴展屬性中, 因此永遠也不會構成引用循環.