本書代碼參考: Learning jQuery Code Listing Browserjavascript
原書: jQuery基礎教程php
第4章 樣式與動畫web
2. 隱藏和顯示元素json
$()函數實際上是建立了一個jQuery對象. 這個函數接受CSS選擇符做爲參數,充當一個工廠, 返回包含頁面中對應元素的jQuery對象. 全部能在樣式表中使用的選擇符均可以傳給這個函數, 隨後就能夠對匹配的元素集合應用jQuery方法.
在jQuery中,美圓符號$其實就是標示符jQuery的"別名".
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
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方法.
.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 參考手冊 - 遍歷
$(document).ready()可簡寫爲: $(); 即:
$(function(){ //code here });
爲了不$的衝突,能夠設置出讓$標示符控制權
jQuery.noConflict(); //之和就經過jQuery來調用jQuery的方法
這種狀況下,能夠在.ready()函數中使用$的技巧----傳遞給ready函數的回調函數能夠接收一個參數----jQuery對象自己,利用這個參數,能夠從新命名jQuery爲$,而沒必要擔憂形成衝突
jQuery(function($){ //使用$的代碼 });
//.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
$(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關鍵字映射到匹配的元素, 不然不會執行事件處理程序.
.off('click')
還能夠爲事件處理程序添加命名空間
$('#switcher').on('click.collapse', function(){}); $('#switcher').off('click.collapse');
對於事件處理系統而言, 後綴.collapse是不可見的.
若是不使用匿名函數,則就能夠不使用事件命名空間, .off()會把命名函數做爲第二個參數, 結果只會解除對特定處理程序的棒的.
爲了獲取某個樣式屬性的值, 能夠爲這個方法傳遞一個字符串形式的屬性名, 而後一樣獲得一個字符串形式的屬性值. 要取得多個樣式屬性的值, 能夠傳入屬性名的數組, 獲得的則是屬性和值勾搭的對象.
對於由多個單詞構成的屬性名, jQuery既能夠解釋連字符版的css表示法(background-color), 也能夠解釋駝峯大小寫形式的DOM表示法(backgroundColor)
對於帶瀏覽器前綴的屬性值(好比-webkit-property-name, -ms-property-name), 用jQuery不須要提早檢測, 而能夠直接使用標準的屬性名. 好比:
.css('propertyName','value'). //若是樣式對象中不存在這個屬性,jQuery就會依次檢測全部帶前綴(Webkit, o, Moz, ms)的屬性, 而後使用第一個找到的那個屬性.
.hide() .show()
能夠爲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();
.animate({property1: 'value1', property2: 'value2'}, duration, easing, function(){ //... });
.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()爲同一組元素應用多重效果, 能夠經過連綴這些效果來實現排隊
//操做非類屬性 .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; } }); });
每次觸發值回調, 都會給它傳入兩個參數. 第一個參數是整數, 表示迭代次數. 第二個參數保存的是修改以前的屬性的值, 這裏並無用到.
HTML屬性和DOM屬性:
在jQuery中, 能夠經過.prop()方法取得和設置DOM屬性:
//取得"checked"屬性的當前值 var currentChecked = $('.my-checkbox').prop('checked'); //設置"checked"屬性的值 $('.my-checkbox').prop('checked', false);
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']);
// 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()方法做爲顯式迭代器, 爲提取腳註的位置加標記和編碼
$('<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>'); });
$('div.chapter p:eq(0').clone().insertBefore('div.chapter');
//要在html中建立新元素, 使用$()標籤 //要在每一個匹配的元素中插入新元素: .append() .appendTo() .prepend() .prependTo() //要在每一個匹配的元素相鄰的位置上插入新元素 .after() .insertAfter() .before() .insertBefore() //要在每一個匹配的元素外部插入新元素 .wrap() .wrapAll() .wrapInner() //要用新元素或文本替換每一個匹配的元素 .html() .text() .replaceAll() .replaceWith() //要移除每一個匹配的元素中的元素 .empty() //要從文檔中移除每一個匹配的元素及其後代元素, 但不實際刪除它們 .remove() .detach()
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(); //事件委託 }); });
爲了防止jQuery的別名$已經被讓渡出去, 能夠在插件的做用域內定義這個快捷方式, 使用當即調用的函數表達式(IIFE, Immediately Invoked Function Expression)
(function($){ //code here })(jQuery);
所謂全局函數, 實際上就是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);
(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);
(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);
這樣, 就能夠在插件方法上連綴內置方法了
所謂內部函數, 就是定義在另外一個函數中的函數. 能夠避免污染命名空間.
而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
當內部函數在定義它的做用域的外部被引出時, 就建立了該內部函數的一個閉包.
這種狀況下, 咱們稱既不是內部函數局部變量, 也不是其參數的變量爲自由變量, 稱外部函數的調用環境爲封閉閉包的環境. 從本質上將, 若是內部函數引用了位於外部函數中的變量, 至關於受權該變量可以被延遲使用. 所以, 當外部函數引用完成後, 這些變量的內存不會被釋放, 由於閉包仍然須要使用它們.
依然要注意如下這個問題:
$(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).
要解決這個問題, 方法有:
$(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變量, 於是每次單擊輸出的值會與元素的標籤文本匹配.
$(document).ready(function() { for(var i=0; i<5; i++){ (function(value){ $('<div>Print '+ value + '</div>') .click(function(){ console.log(value); }).insertBefore('body'); })(i); } });
$(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是函數的參數, 每次調用處理程序時它都是一個獨立的實例, 而不是在全部調用中共享的一個值.
閉包可能會致使在不經意間建立引用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()所引用. 所以, 閉包會使意外地建立這些引用循環變得容易.
以上這種狀況一般容易處理, 由於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), 不存在的循環, 就不會形成內存泄露了.
$(document).ready(function() { var $button = $('#button-1'); $button.click(function(event) { event.preventDefault(); console.log('hello'); }); });
即便此時仍然會建立一個閉包, 而且也會致使同前面同樣的循環, 但這裏的代碼卻不會使IE發生內存泄露. 因爲jQuery考慮到了內存泄露的潛在危害, 因此它會手動釋放本身指定的全部事件處理程序.
另外一種避免泄露的工具-----使用.data()方法能夠像使用擴展屬性同樣, 將信息附加到DOM元素. 因爲這裏的數據並不是直接保存在擴展屬性中, 所以永遠也不會構成引用循環.