<h2 id="11.1">概述</h2>
jQuery是目前使用最廣泛的JavaScript函數庫。據[統計](http://w3techs.com/technologies/details/js-jquery/all/all),全世界57.5%的網站使用jQuery,在使用JavaScript函數庫的網站中,93.0%使用jQuery。它已經成了開發者必須學會的技能。
jQuery的最大優勢有兩個。首先,它基本是一個DOM操作工具,可以使操作DOM對象變得異常容易。其次,它統一了不同瀏覽器的API接口,使得代碼在所有現代瀏覽器均能運行,開發者不用擔心瀏覽器之間的差異。
## jQuery的加載
一般采用下面的寫法,在網頁中加載jQuery。
```html
<script type="text/javascript"
src="//code.jquery.com/jquery-1.11.0.min.js">
</script>
<script>
window.jQuery ||
document.write(
'<script src="js/jquery-1.11.0.min.js" type="text/javascript"><\/script>'
);
</script>
```
上面代碼有兩點需要注意。一是采用[CDN](http://jquery.com/download/#using-jquery-with-a-cdn)加載。如果CDN加載失敗,則退回到本地加載。二是采用協議無關的加載網址(使用雙斜線表示),同時支持http協議和https協議。
目前常用的jQuery CDN有以下這些。
- [Google CDN](https://developers.google.com/speed/libraries/devguide#jquery)
- [Microsoft CDN](http://www.asp.net/ajax/cdn#jQuery_Releases_on_the_CDN_0)
- [jQuery CDN](http://jquery.com/download/#jquery-39-s-cdn-provided-by-maxcdn)
- [CDNJS CDN](http://cdnjs.com/libraries/jquery/)
- [jsDelivr CDN](http://www.jsdelivr.com/#!jquery)
上面這段代碼最好放到網頁尾部。如果需要支持IE 6/7/8,就使用jQuery 1.x版,否則使用最新版。
## jQuery基礎
### jQuery對象
jQuery最重要的概念,就是`jQuery`對象。它是一個全局對象,可以簡寫為美元符號`$`。也就是說,`jQuery`和`$`兩者是等價的。
在網頁中加載jQuery函數庫以后,就可以使用jQuery對象了。jQuery的全部方法,都定義在這個對象上面。
```javascript
var listItems = jQuery('li');
// or
var listItems = $('li');
```
上面兩行代碼是等價的,表示選中網頁中所有的`li`元素。
### jQuery構造函數
`jQuery`對象本質上是一個構造函數,主要作用是返回`jQuery`對象的實例。比如,上面代碼表面上是選中`li`元素,實際上是返回對應于`li`元素的`jQuery`實例。因為只有這樣,才能在DOM對象上使用jQuery提供的各種方法。
```javascript
$('body').nodeType
// undefined
$('body') instanceof jQuery
// true
```
上面代碼表示,由于jQuery返回的不是DOM對象,所以沒有DOM屬性`nodeType`。它返回的是jQuery對象的實例。
jQuery構造函數可以多種參數,返回不同的值。
**(1)CSS選擇器作為參數**
jQuery構造函數的參數,主要是CSS選擇器,就像上面的那個例子。下面是另外一些CSS選擇器的例子。
```javascript
$("*")
$("#lastname")
$(".intro")
$("h1,div,p")
$("p:last")
$("tr:even")
$("p:first-child")
$("p:nth-of-type(2)")
$("div + p")
$("div:has(p)")
$(":empty")
$("[title^='Tom']")
```
本書不講解CSS選擇器,請讀者參考有關書籍和jQuery文檔。
除了CSS選擇器,jQuery還定義了一些自有的選擇器,比如`contains`選擇器用來選擇包含特定文本的元素。下面是一個例子。
```javascript
var search = $('#search').val();
$('div:not(:contains("' + search + '"))').hide();
```
上面代碼用來選中包含搜索框輸入文本的元素。
**(2)DOM對象作為參數**
jQuery構造函數的參數,還可以是DOM對象。它也會被轉為jQuery對象的實例。
```javascript
$(document.body) instanceof jQuery
// true
```
上面代碼中,jQuery的參數不是CSS選擇器,而是一個DOM對象,返回的依然是jQuery對象的實例。
如果有多個DOM元素要轉為jQuery對象的實例,可以把DOM元素放在一個數組里,輸入jQuery構造函數。
```javascript
$([document.body, document.head])
```
**(3)HTML字符串作為參數**
如果直接在jQuery構造函數中輸入HTML字符串,返回的也是jQuery實例。
```javascript
$('<li class="greet">test</li>')
```
上面代碼從HTML代碼生成了一個jQuery實例,它與從CSS選擇器生成的jQuery實例完全一樣。唯一的區別就是,它對應的DOM結構不屬于當前文檔。
上面代碼也可以寫成下面這樣。
```javascript
$( '<li>', {
html: 'test',
'class': 'greet'
});
```
上面代碼中,由于`class`是javaScript的保留字,所以只能放在引號中。
通常來說,上面第二種寫法是更好的寫法。
```javascript
$('<input class="form-control" type="hidden" name="foo" value="bar" />')
// 相當于
$('<input/>', {
'class': 'form-control',
type: 'hidden',
name: 'foo',
value: 'bar'
})
// 或者
$('<input/>')
.addClass('form-control')
.attr('type', 'hidden')
.attr('name', 'foo')
.val('bar')
```
由于新增的DOM節點不屬于當前文檔,所以可以用這種寫法預加載圖片。
```javascript
$.preloadImages = function () {
for (var i = 0; i < arguments.length; i++) {
$('<img>').attr('src', arguments[i]);
}
};
$.preloadImages('img/hover-on.png', 'img/hover-off.png');
```
**(4)第二個參數**
默認情況下,jQuery將文檔的根元素(`html`)作為尋找匹配對象的起點。如果要指定某個網頁元素(比如某個`div`元素)作為尋找的起點,可以將它放在jQuery函數的第二個參數。
```javascript
$('li', someElement);
```
上面代碼表示,只尋找屬于someElement對象下屬的li元素。someElement可以是jQuery對象的實例,也可以是DOM對象。
### jQuery構造函數返回的結果集
jQuery的核心思想是“先選中某些網頁元素,然后對其進行某種處理”(find something, do something),也就是說,先選擇后處理,這是jQuery的基本操作模式。所以,絕大多數jQuery操作都是從選擇器開始的,返回一個選中的結果集。
**(1)length屬性**
jQuery對象返回的結果集是一個類似數組的對象,包含了所有被選中的網頁元素。查看該對象的length屬性,可以知道到底選中了多少個結果。
```javascript
if ( $('li').length === 0 ) {
console.log('不含li元素');
}
```
上面代碼表示,如果網頁沒有li元素,則返回對象的length屬性等于0。這就是測試有沒有選中的標準方法。
所以,如果想知道jQuery有沒有選中相應的元素,不能寫成下面這樣。
```javascript
if ($('div.foo')) { ... }
```
因為不管有沒有選中,jQuery構造函數總是返回一個實例對象,而對象的布爾值永遠是true。使用length屬性才是判斷有沒有選中的正確方法。
```javascript
if ($('div.foo').length) { ... }
```
**(2)下標運算符**
jQuery選擇器返回的是一個類似數組的對象。但是,使用下標運算符取出的單個對象,并不是jQuery對象的實例,而是一個DOM對象。
```javascript
$('li')[0] instanceof jQuery // false
$('li')[0] instanceof Element // true
```
上面代碼表示,下標運算符取出的是Element節點的實例。所以,通常使用下標運算符將jQuery實例轉回DOM對象。
**(3)is方法**
is方法返回一個布爾值,表示選中的結果是否符合某個條件。這個用來驗證的條件,可以是CSS選擇器,也可以是一個函數,或者DOM元素和jQuery實例。
```javascript
$('li').is('li') // true
$('li').is($('.item'))
$('li').is(document.querySelector('li'))
$('li').is(function() {
return $("strong", this).length === 0;
});
```
**(4)get方法**
jQuery實例的get方法是下標運算符的另一種寫法。
```javascript
$('li').get(0) instanceof Element // true
```
**(5)eq方法**
如果想要在結果集取出一個jQuery對象的實例,不需要取出DOM對象,則使用eq方法,它的參數是實例在結果集中的位置(從0開始)。
```javascript
$('li').eq(0) instanceof jQuery // true
```
由于eq方法返回的是jQuery的實例,所以可以在返回結果上使用jQuery實例對象的方法。
**(6)each方法,map方法**
這兩個方法用于遍歷結果集,對每一個成員進行某種操作。
each方法接受一個函數作為參數,依次處理集合中的每一個元素。
```javascript
$('li').each(function( index, element) {
$(element).prepend( '<em>' + index + ': </em>' );
});
// <li>Hello</li>
// <li>World</li>
// 變為
// <li><em>0: </em>Hello</li>
// <li><em>1: </em>World</li>
```
從上面代碼可以看出,作為each方法參數的函數,本身有兩個參數,第一個是當前元素在集合中的位置,第二個是當前元素對應的DOM對象。
map方法的用法與each方法完全一樣,區別在于each方法沒有返回值,只是對每一個元素執行某種操作,而map方法返回一個新的jQuery對象。
```javascript
$("input").map(function (index, element){
return $(this).val();
})
.get()
.join(", ")
```
上面代碼表示,將所有input元素依次取出值,然后通過get方法得到一個包含這些值的數組,最后通過數組的join方法返回一個逗號分割的字符串。
**(8)內置循環**
jQuery默認對當前結果集進行循環處理,所以如果直接使用jQuery內置的某種方法,each和map方法是不必要的。
```javascript
$(".class").addClass("highlight");
```
上面代碼會執行一個內部循環,對每一個選中的元素進行addClass操作。由于這個原因,對上面操作加上each方法是不必要的。
```javascript
$(".class").each(function(index,element){
$(element).addClass("highlight");
});
// 或者
$(".class").each(function(){
$(this).addClass("highlight");
});
```
上面代碼的each方法,都是沒必要使用的。
由于內置循環的存在,從性能考慮,應該盡量減少不必要的操作步驟。
```javascript
$(".class").css("color", "green").css("font-size", "16px");
// 應該寫成
$(".class").css({
"color": "green",
"font-size": "16px"
});
```
### 鏈式操作
jQuery最方便的一點就是,它的大部分方法返回的都是jQuery對象,因此可以鏈式操作。也就是說,后一個方法可以緊跟著寫在前一個方法后面。
```javascript
$('li').click(function (){
$(this).addClass('clicked');
})
.find('span')
.attr( 'title', 'Hover over me' );
```
### $(document).ready()
$(document).ready方法接受一個函數作為參數,將該參數作為document對象的DOMContentLoaded事件的回調函數。也就是說,當頁面解析完成(即下載完</html>標簽)以后,在所有外部資源(圖片、腳本等)完成加載之前,該函數就會立刻運行。
```javascript
$( document ).ready(function() {
console.log( 'ready!' );
});
```
上面代碼表示,一旦頁面完成解析,就會運行ready方法指定的函數,在控制臺顯示“ready!”。
該方法通常作為網頁初始化手段使用,jQuery提供了一種簡寫法,就是直接把回調函數放在jQuery對象中。
```javascript
$(function() {
console.log( 'ready!' );
});
```
上面代碼與前一段代碼是等價的。
### $.noConflict方法
jQuery使用美元符號($)指代jQuery對象。某些情況下,其他函數庫也會用到美元符號,為了避免沖突,$.noConflict方法允許將美元符號與jQuery脫鉤。
```html
<script src="other_lib.js"></script>
<script src="jquery.js"></script>
<script>$.noConflict();</script>
```
上面代碼就是$.noConflict方法的一般用法。在加載jQuery之后,立即調用該方法,會使得美元符號還給前面一個函數庫。這意味著,其后再調用jQuery,只能寫成jQuery.methond的形式,而不能用$.method了。
為了避免沖突,可以考慮從一開始就只使用jQuery代替美元符號。
## jQuery實例對象的方法
除了上一節提到的is、get、eq方法,jQuery實例還有許多其他方法。
### 結果集的過濾方法
選擇器選出一組符合條件的網頁元素以后,jQuery提供了許多方法,可以過濾結果集,返回更準確的目標。
**(1)first方法,last方法**
first方法返回結果集的第一個成員,last方法返回結果集的最后一個成員。
```javascri
$("li").first()
$("li").last()
```
**(2)next方法,prev方法**
next方法返回緊鄰的下一個同級元素,prev方法返回緊鄰的上一個同級元素。
```javascript
$("li").first().next()
$("li").last().prev()
$("li").first().next('.item')
$("li").last().prev('.item')
```
如果`next`方法和`prev`方法帶有參數,表示選擇符合該參數的同級元素。
**(3)parent方法,parents方法,children方法**
parent方法返回當前元素的父元素,parents方法返回當前元素的所有上級元素(直到html元素)。
```javascript
$("p").parent()
$("p").parent(".selected")
$("p").parents()
$("p").parents("div")
```
children方法返回選中元素的所有子元素。
```javascript
$("div").children()
$("div").children(".selected")
// 下面的寫法結果相同,但是效率較低
$('div > *')
$('div > .selected')
```
上面這三個方法都接受一個選擇器作為參數。
**(4)siblings方法,nextAll方法,prevAll方法**
siblings方法返回當前元素的所有同級元素。
```javascript
$('li').first().siblings()
$('li').first().siblings('.item')
```
nextAll方法返回當前元素其后的所有同級元素,prevAll方法返回當前元素前面的所有同級元素。
```javascript
$('li').first().nextAll()
$('li').last().prevAll()
```
**(5)closest方法,find方法**
closest方法返回當前元素,以及當前元素的所有上級元素之中,第一個符合條件的元素。find方法返回當前元素的所有符合條件的下級元素。
```javascript
$('li').closest('div')
$('div').find('li')
```
上面代碼中的find方法,選中所有div元素下面的li元素,等同于$('li', 'div')。由于這樣寫縮小了搜索范圍,所以要優于$('div li')的寫法。
**(6)find方法,add方法,addBack方法,end方法**
add方法用于為結果集添加元素。
```javascript
$('li').add('p')
```
addBack方法將當前元素加回原始的結果集。
```javascript
$('li').parent().addBack()
```
end方法用于返回原始的結果集。
```javascrip
$('li').first().end()
```
**(7)filter方法,not方法,has方法**
filter方法用于過濾結果集,它可以接受多種類型的參數,只返回與參數一致的結果。
```javascript
// 返回符合CSS選擇器的結果
$('li').filter('.item')
// 返回函數返回值為true的結果
$("li").filter(function(index) {
return index % 2 === 1;
})
// 返回符合特定DOM對象的結果
$("li").filter(document.getElementById("unique"))
// 返回符合特定jQuery實例的結果
$("li").filter($("#unique"))
```
`not`方法的用法與`filter`方法完全一致,但是返回相反的結果,即過濾掉匹配項。
```javascript
$('li').not('.item')
```
has方法與filter方法作用相同,但是只過濾出子元素符合條件的元素。
```javascript
$("li").has("ul")
```
上面代碼返回具有ul子元素的li元素。
### DOM相關方法
許多方法可以對DOM元素進行處理。
**(1)html方法和text方法**
html方法返回該元素包含的HTML代碼,text方法返回該元素包含的文本。
假定網頁只含有一個p元素。
```html
<p><em>Hello World!</em></p>
```
html方法和text方法的返回結果分別如下。
```javascript
$('p').html()
// <em>Hello World!</em>
$('p').text()
// Hello World!
```
jQuery的許多方法都是取值器(getter)與賦值器(setter)的合一,即取值和賦值都是同一個方法,不使用參數的時候為取值器,使用參數的時候為賦值器。
上面代碼的html方法和text方法都沒有參數,就會當作取值器使用,取回結果集的第一個元素所包含的內容。如果對這兩個方法提供參數,就是當作賦值器使用,修改結果集所有成員的內容,并返回原來的結果集,以便進行鏈式操作。
```javascript
$('p').html('<strong>你好</strong>')
// 網頁代碼變為<p><strong>你好</strong></p>
$('p').text('你好')
// 網頁代碼變為<p>你好</p>
```
下面要講到的jQuery其他許多方法,都采用這種同一個方法既是取值器又是賦值器的模式。
html方法和text方法還可以接受一個函數作為參數,函數的返回值就是網頁元素所要包含的新的代碼和文本。這個函數接受兩個參數,第一個是網頁元素在集合中的位置,第二個參數是網頁元素原來的代碼或文本。
```javascript
$('li').html(function (i, v){
return (i + ': ' + v);
})
// <li>Hello</li>
// <li>World</li>
// 變為
// <li>0: Hello</li>
// <li>1: World</li>
```
**(2)addClass方法,removeClass方法,toggleClass方法**
addClass方法用于添加一個類,removeClass方法用于移除一個類,toggleClass方法用于折疊一個類(如果無就添加,如果有就移除)。
```javascript
$('li').addClass('special')
$('li').removeClass('special')
$('li').toggleClass('special')
```
**(3)css方法**
css方法用于改變CSS設置。
該方法可以作為取值器使用。
```javascript
$('h1').css('fontSize');
```
css方法的參數是css屬性名。這里需要注意,CSS屬性名的CSS寫法和DOM寫法,兩者都可以接受,比如font-size和fontSize都行。
css方法也可以作為賦值器使用。
```javascript
$('li').css('padding-left', '20px')
// 或者
$('li').css({
'padding-left': '20px'
});
```
上面兩種形式都可以用于賦值,jQuery賦值器基本上都是如此。
**(4)val方法**
val方法返回結果集第一個元素的值,或者設置當前結果集所有元素的值。
```javascript
$('input[type="text"]').val()
$('input[type="text"]').val('new value')
```
**(5)prop方法,attr方法**
首先,這里要區分兩種屬性。
一種是網頁元素的屬性,比如`a`元素的`href`屬性、`img`元素的`src`屬性。這要使用`attr`方法讀寫。
```javascript
// 讀取屬性值
$('textarea').attr(name)
//寫入屬性值
$('textarea').attr(name, val)
```
下面是通過設置`a`元素的`target`屬性,使得網頁上的外部鏈接在新窗口打開的例子。
```javascript
$('a[href^="http"]').attr('target', '_blank');
$('a[href^="//"]').attr('target', '_blank');
$('a[href^="' + window.location.origin + '"]').attr('target', '_self');
```
另一種是DOM元素的屬性,比如`tagName`、`nodeName`、`nodeType`等等。這要使用`prop`方法讀寫。
```javascript
// 讀取屬性值
$('textarea').prop(name)
// 寫入屬性值
$('textarea').prop(name, val)
```
所以,`attr`方法和`prop`方法針對的是不同的屬性。在英語中,`attr`是attribute的縮寫,`prop`是property的縮寫,中文很難表達出這種差異。有時,`attr`方法和`prop`方法對同一個屬性會讀到不一樣的值。比如,網頁上有一個單選框。
```html
<input type="checkbox" checked="checked" />
```
對于checked屬性,attr方法讀到的是checked,prop方法讀到的是true。
```javascript
$(input[type=checkbox]).attr("checked") // "checked"
$(input[type=checkbox]).prop("checked") // true
```
可以看到,attr方法讀取的是網頁上該屬性的值,而prop方法讀取的是DOM元素的該屬性的值,根據規范,element.checked應該返回一個布爾值。所以,判斷單選框是否選中,要使用prop方法。事實上,不管這個單選框是否選中,attr("checked")的返回值都是checked。
```javascript
if ($(elem).prop("checked")) { /*... */ };
// 下面兩種方法亦可
if ( elem.checked ) { /*...*/ };
if ( $(elem).is(":checked") ) { /*...*/ };
```
**(6)removeProp方法,removeAttr方法**
removeProp方法移除某個DOM屬性,removeAttr方法移除某個HTML屬性。
```javascript
$("a").prop("oldValue",1234).removeProp('oldValue')
$('a').removeAttr("title")
```
**(7)data方法**
data方法用于在一個DOM對象上儲存數據。
```javascript
// 儲存數據
$("body").data("foo", 52);
// 讀取數據
$("body").data("foo");
```
該方法可以在DOM節點上儲存各種類型的數據。
### 添加、復制和移動網頁元素的方法
jQuery方法提供一系列方法,可以改變元素在文檔中的位置。
**(1)append方法,appendTo方法**
append方法將參數中的元素插入當前元素的尾部。
```javascript
$("div").append("<p>World</p>")
// <div>Hello </div>
// 變為
// <div>Hello <p>World</p></div>
```
appendTo方法將當前元素插入參數中的元素尾部。
```javascript
$("<p>World</p>").appendTo("div")
```
上面代碼返回與前一個例子一樣的結果。
**(2)prepend方法,prependTo方法**
prepend方法將參數中的元素,變為當前元素的第一個子元素。
```javascript
$("p").prepend("Hello ")
// <p>World</p>
// 變為
// <p>Hello World</p>
```
如果prepend方法的參數不是新生成的元素,而是當前頁面已存在的元素,則會產生移動元素的效果。
```javascript
$("p").prepend("strong")
// <strong>Hello </strong><p>World</p>
// 變為
// <p><strong>Hello </strong>World</p>
```
上面代碼運行后,strong元素的位置將發生移動,而不是克隆一個新的strong元素。不過,如果當前結果集包含多個元素,則除了第一個以后,后面的p元素都將插入一個克隆的strong子元素。
prependTo方法將當前元素變為參數中的元素的第一個子元素。
```javascript
$("<p></p>").prependTo("div")
// <div></div>
// 變為
// <div><p></p></div>
```
**(3)after方法,insertAfter方法**
after方法將參數中的元素插在當前元素后面。
```javascript
$("div").after("<p></p>")
// <div></div>
// 變為
// <div></div><p></p>
```
insertAfter方法將當前元素插在參數中的元素后面。
```javascript
$("<p></p>").insertAfter("div")
```
上面代碼返回與前一個例子一樣的結果。
**(4)before方法,insertBefore方法**
before方法將參數中的元素插在當前元素的前面。
```javascript
$("div").before("<p></p>")
// <div></div>
// 變為
// <p></p><div></div>
```
insertBefore方法將當前元素插在參數中的元素的前面。
```javascript
$("<p></p>").insertBefore("div")
```
上面代碼返回與前一個例子一樣的結果。
**(5)wrap方法,wrapAll方法,unwrap方法,wrapInner方法**
wrap方法將參數中的元素變成當前元素的父元素。
```javascript
$("p").wrap("<div></div>")
// <p></p>
// 變為
// <div><p></p></div>
```
wrap方法的參數還可以是一個函數。
```javascript
$("p").wrap(function() {
return "<div></div>";
})
```
上面代碼返回與前一個例子一樣的結果。
wrapAll方法為結果集的所有元素,添加一個共同的父元素。
```javascript
$("p").wrapAll("<div></div>")
// <p></p><p></p>
// 變為
// <div><p></p><p></p></div>
```
unwrap方法移除當前元素的父元素。
```javascript
$("p").unwrap()
// <div><p></p></div>
// 變為
// <p></p>
```
wrapInner方法為當前元素的所有子元素,添加一個父元素。
```javascript
$("p").wrapInner('<strong></strong>')
// <p>Hello</p>
// 變為
// <p><strong>Hello</strong></p>
```
**(6)clone方法**
clone方法克隆當前元素。
```javascript
var clones = $('li').clone();
```
對于那些有id屬性的節點,clone方法會連id屬性一起克隆。所以,要把克隆的節點插入文檔的時候,務必要修改或移除id屬性。
**(7)remove方法,detach方法,replaceWith方法**
remove方法移除并返回一個元素,取消該元素上所有事件的綁定。detach方法也是移除并返回一個元素,但是不取消該元素上所有事件的綁定。
```javascript
$('p').remove()
$('p').detach()
```
replaceWith方法用參數中的元素,替換并返回當前元素,取消當前元素的所有事件的綁定。
```javascript
$('p').replaceWith('<div></div>')
```
### 動畫效果方法
jQuery提供一些方法,可以很容易地顯示網頁動畫效果。但是,總體上來說,它們不如CSS動畫強大和節省資源,所以應該優先考慮使用CSS動畫。
如果將jQuery.fx.off設為true,就可以將所有動畫效果關閉,使得網頁元素的各種變化一步到位,沒有中間過渡的動畫效果。
**(1)動畫效果的簡便方法**
jQuery提供以下一些動畫效果方法。
- show:顯示當前元素。
- hide:隱藏當前元素。
- toggle:顯示或隱藏當前元素。
- fadeIn:將當前元素的不透明度(opacity)逐步提升到100%。
- fadeOut:將當前元素的不透明度逐步降為0%。
- fadeToggle:以逐漸透明或逐漸不透明的方式,折疊顯示當前元素。
- slideDown:以從上向下滑入的方式顯示當前元素。
- slideUp:以從下向上滑出的方式隱藏當前元素。
- slideToggle:以垂直滑入或滑出的方式,折疊顯示當前元素。
上面這些方法可以不帶參數調用,也可以接受毫秒或預定義的關鍵字作為參數。
```javascript
$('.hidden').show();
$('.hidden').show(300);
$('.hidden').show('slow');
```
上面三行代碼分別表示,以默認速度、300毫秒、較慢的速度隱藏一個元素。
jQuery預定義的關鍵字是在`jQuery.fx.speeds`對象上面,可以自行改動這些值,或者創造新的值。
```javascript
jQuery.fx.speeds.fast = 50;
jQuery.fx.speeds.slow = 3000;
jQuery.fx.speeds.normal = 1000;
```
上面三行代碼重新定義fast、normal、slow關鍵字對應的毫秒數。
你還可以定義自己的關鍵字。
```javascript
jQuery.fx.speeds.blazing = 30;
// 調用
$('.hidden').show('blazing');
```
這些方法還可以接受一個函數,作為第二個參數,表示動畫結束后的回調函數。
```javascript
$('p').fadeOut(300, function() {
$(this).remove();
});
```
上面代碼表示,`p`元素以300毫秒的速度淡出,然后調用回調函數,將其從DOM中移除。
使用按鈕控制某個元素折疊顯示的代碼如下。
```javascript
// Fade
$('.btn').click(function () {
$('.element').fadeToggle('slow');
});
// Toggle
$('.btn').click(function () {
$('.element').slideToggle('slow');
});
```
**(2)animate方法**
上面這些動畫效果方法,實際上都是animate方法的簡便寫法。在幕后,jQuery都是統一使用animate方法生成各種動畫效果。
```javascript
$('a.top').click(function (e) {
e.preventDefault();
$('html, body').animate({scrollTop: 0}, 800);
});
```
上面代碼是點擊鏈接,回到頁面頭部的寫法。其中,`animate`方法接受兩個參數,第一個參數是一個對象,表示動畫結束時相關CSS屬性的值,第二個參數是動畫持續的毫秒數。需要注意的是,第一個參數對象的成員名稱,必須與CSS屬性名稱一致,如果CSS屬性名稱帶有連字號,則需要用“駱駝拼寫法”改寫。
animate方法還可以接受第三個參數,表示動畫結束時的回調函數。
```javascript
$('div').animate({
left: '+=50', // 增加50
opacity: 0.25,
fontSize: '12px'
},
300, // 持續時間
function() { // 回調函數
console.log('done!');
}
);
```
上面代碼表示,動畫結束時,在控制臺輸出“done!”。
**(3)stop方法,delay方法**
stop方法表示立即停止執行當前的動畫。
```javascript
$("#stop").click(function() {
$(".block").stop();
});
```
上面代碼表示,點擊按鈕后,block元素的動畫效果停止。
delay方法接受一個時間參數,表示暫停多少毫秒后繼續執行。
```javascript
$("#foo").slideUp(300).delay(800).fadeIn(400)
```
上面代碼表示,slideUp動畫之后,暫停800毫秒,然后繼續執行fadeIn動畫。
### 其他方法
jQuery還提供一些供特定元素使用的方法。
serialize方法用于將表單元素的值,轉為url使用的查詢字符串。
```javascript
$( "form" ).on( "submit", function( event ) {
event.preventDefault();
console.log( $( this ).serialize() );
});
// single=Single&multiple=Multiple&check=check2&radio=radio1
```
serializeArray方法用于將表單元素的值轉為數組。
```javascript
$("form").submit(function (event){
console.log($(this).serializeArray());
event.preventDefault();
});
// [
// {name : 'field1', value : 123},
// {name : 'field2', value : 'hello world'}
// ]
```
## 事件處理
### 事件綁定的簡便方法
jQuery提供一系列方法,允許直接為常見事件綁定回調函數。比如,click方法可以為一個元素綁定click事件的回調函數。
```javascript
$('li').click(function (e){
console.log($(this).text());
});
```
上面代碼為li元素綁定click事件的回調函數,點擊后在控制臺顯示li元素包含的文本。
這樣綁定事件的簡便方法有如下一些:
- click
- keydown
- keypress
- keyup
- mouseover
- mouseout
- mouseenter
- mouseleave
- scroll
- focus
- blur
- resize
- hover
如果不帶參數調用這些方法,就是觸發相應的事件,從而引發回調函數的運行。
```javascript
$('li').click()
```
上面代碼將觸發click事件的回調函數。
需要注意的是,通過這種方法觸發回調函數,將不會引發瀏覽器對該事件的默認行為。比如,對a元素調用click方法,將只觸發事先綁定的回調函數,而不會導致瀏覽器將頁面導向href屬性指定的網址。
下面是一個捕捉用戶按下escape鍵的函數。
```javascript
$(document).keyup(function(e) {
if (e.keyCode == 27) {
$('body').toggleClass('show-nav');
// $('body').removeClass('show-nav');
}
});
```
上面代碼中,用戶按下escape鍵,jQuery就會為body元素添加/去除名為show-nav的class。
`hover`方法需要特別說明。它接受兩個回調函數作為參數,分別代表`mouseenter`和`mouseleave`事件的回調函數。
```javascript
$(selector).hover(handlerIn, handlerOut)
// 等同于
$(selector).mouseenter(handlerIn).mouseleave(handlerOut)
```
下面是一個例子,當按鈕發生`hover`事件,添加一個class樣式,當`hover`事件結束時,再取消這個class。
```javascript
$('.btn').hover(function () {
$(this).addClass('hover');
}, function () {
$(this).removeClass('hover');
});
```
使用`toggleClass`可以簡化上面的代碼。
```javascript
$('.btn').hover(function () {
$(this).toggleClass('hover');
});
```
### on方法,trigger方法,off方法
除了簡便方法,jQuery還提供事件處理的通用方法。
**(1)on方法**
`on`方法是jQuery事件綁定的統一接口。事件綁定的那些簡便方法,其實都是`on`方法的簡寫形式。
`on`方法接受兩個參數,第一個是事件名稱,第二個是回調函數。
```javascript
$('li').on('click', function (e){
console.log($(this).text());
});
```
上面代碼為`li`元素綁定`click`事件的回調函數。
> 注意,在回調函數內部,`this`關鍵字指的是發生該事件的DOM對象。為了使用jQuery提供的方法,必須將DOM對象轉為jQuery對象,因此寫成`$(this)`。
`on`方法允許一次為多個事件指定同樣的回調函數。
```javascript
$('input[type="text"]').on('focus blur', function (){
console.log('focus or blur');
});
```
上面代碼為文本框的`focus`和`blur`事件綁定同一個回調函數。
下面是一個例子,當圖片加載失敗,使用`error`事件,替換另一張圖片。
```javascript
$('img').on('error', function () {
if(!$(this).hasClass('broken-image')) {
$(this).prop('src', 'img/broken.png').addClass('broken-image');
}
});
```
下面是檢查用戶是否切換瀏覽器tab的例子。
```javascript
$(document).on('visibilitychange', function (e) {
if (e.target.visibilityState === "visible") {
console.log('Tab is now in view!');
} else if (e.target.visibilityState === "hidden") {
console.log('Tab is now hidden!');
}
});
```
`on`方法還可以為當前元素的某一個子元素,添加回調函數。
```javascript
$('ul').on('click', 'li', function (e){
console.log(this);
});
```
上面代碼為`ul`的子元素`li`綁定click事件的回調函數。采用這種寫法時,on方法接受三個參數,子元素選擇器作為第二個參數,夾在事件名稱和回調函數之間。
這種寫法有兩個好處。首先,click事件還是在ul元素上觸發回調函數,但是會檢查event對象的target屬性是否為li子元素,如果為true,再調用回調函數。這樣就比為li元素一一綁定回調函數,節省了內存空間。其次,這種綁定的回調函數,對于在綁定后生成的li元素依然有效。
on方法還允許向回調函數傳入數據。
```javascript
$("ul" ).on("click", {name: "張三"}, function (event){
console.log(event.data.name);
});
```
上面代碼在發生click事件之后,會通過event對象的data屬性,在控制臺打印出所傳入的數據(即“張三”)。
**(2)trigger方法**
trigger方法用于觸發回調函數,它的參數就是事件的名稱。
```javascript
$('li').trigger('click')
```
上面代碼觸發li元素的click事件回調函數。與那些簡便方法一樣,trigger方法只觸發回調函數,而不會引發瀏覽器的默認行為。
**(3)off方法**
off方法用于移除事件的回調函數。
```javascript
$('li').off('click')
```
上面代碼移除li元素所有的click事件回調函數。
**(4)事件的名稱空間**
同一個事件有時綁定了多個回調函數,這時如果想移除其中的一個回調函數,可以采用“名稱空間”的方式,即為每一個回調函數指定一個二級事件名,然后再用off方法移除這個二級事件的回調函數。
```javascript
$('li').on('click.logging', function (){
console.log('click.logging callback removed');
});
$('li').off('click.logging');
```
上面代碼為li元素定義了二級事件click.logging的回調函數,click.logging屬于click名稱空間,當發生click事件時會觸發該回調函數。將click.logging作為off方法的參數,就會移除這個回調函數,但是對其他click事件的回調函數沒有影響。
trigger方法也適用帶名稱空間的事件。
```javascript
$('li').trigger('click.logging')
```
### event對象
當回調函數被觸發后,它們的參數通常是一個事件對象event。
```javascript
$(document).on('click', function (e){
// ...
});
```
上面代碼的回調函數的參數e,就代表事件對象event。
event對象有以下屬性:
- type:事件類型,比如click。
- which:觸發該事件的鼠標按鈕或鍵盤的鍵。
- target:事件發生的初始對象。
- data:傳入事件對象的數據。
- pageX:事件發生時,鼠標位置的水平坐標(相對于頁面左上角)。
- pageY:事件發生時,鼠標位置的垂直坐標(相對于頁面左上角)。
event對象有以下方法:
- preventDefault:取消瀏覽器默認行為。
- stopPropagation:阻止事件向上層元素傳播。
### 一次性事件
one方法指定一次性的回調函數,即這個函數只能運行一次。這對提交表單很有用。
```javascript
$("#button").one( "click", function() { return false; } );
```
one方法本質上是回調函數運行一次,即解除對事件的監聽。
```javascript
document.getElementById("#button").addEventListener("click", handler);
function handler(e) {
e.target.removeEventListener(e.type, arguments.callee);
return false;
}
```
上面的代碼在點擊一次以后,取消了對click事件的監聽。如果有特殊需要,可以設定點擊2次或3次之后取消監聽,這都是可以的。
<h2 id="11.2">jQuery工具方法</h2>
jQuery函數庫提供了一個jQuery對象(簡寫為$),這個對象本身是一個構造函數,可以用來生成jQuery對象的實例。有了實例以后,就可以調用許多針對實例的方法,它們定義jQuery.prototype對象上面(簡寫為$.fn)。
除了實例對象的方法以外,jQuery對象本身還提供一些方法(即直接定義jQuery對象上面),不需要生成實例就能使用。由于這些方法類似“通用工具”的性質,所以我們把它們稱為“工具方法”(utilities)。
## 常用工具方法
**(1)$.trim**
$.trim方法用于移除字符串頭部和尾部多余的空格。
```javascript
$.trim(' Hello ') // Hello
```
**(2)$.contains**
$.contains方法返回一個布爾值,表示某個DOM元素(第二個參數)是否為另一個DOM元素(第一個參數)的下級元素。
```javascript
$.contains(document.documentElement, document.body);
// true
$.contains(document.body, document.documentElement);
// false
```
**(3)$.each,$.map**
$.each方法用于遍歷數組和對象,然后返回原始對象。它接受兩個參數,分別是數據集合和回調函數。
```javascript
$.each([ 52, 97 ], function( index, value ) {
console.log( index + ": " + value );
});
// 0: 52
// 1: 97
var obj = {
p1: "hello",
p2: "world"
};
$.each( obj, function( key, value ) {
console.log( key + ": " + value );
});
// p1: hello
// p2: world
```
需要注意的,jQuery對象實例也有一個each方法($.fn.each),兩者的作用差不多。
$.map方法也是用來遍歷數組和對象,但是會返回一個新對象。
```javascript
var a = ["a", "b", "c", "d", "e"];
a = $.map(a, function (n, i){
return (n.toUpperCase() + i);
});
// ["A0", "B1", "C2", "D3", "E4"]
```
**(4)$.inArray**
$.inArray方法返回一個值在數組中的位置(從0開始)。如果該值不在數組中,則返回-1。
```javascript
var a = [1,2,3,4];
$.inArray(4,a) // 3
```
**(5)$.extend**
$.extend方法用于將多個對象合并進第一個對象。
```javascript
var o1 = {p1:'a',p2:'b'};
var o2 = {p1:'c'};
$.extend(o1,o2);
o1.p1 // "c"
```
$.extend的另一種用法是生成一個新對象,用來繼承原有對象。這時,它的第一個參數應該是一個空對象。
```javascript
var o1 = {p1:'a',p2:'b'};
var o2 = {p1:'c'};
var o = $.extend({},o1,o2);
o
// Object {p1: "c", p2: "b"}
```
默認情況下,extend方法生成的對象是“淺拷貝”,也就是說,如果某個屬性是對象或數組,那么只會生成指向這個對象或數組的指針,而不會復制值。如果想要“深拷貝”,可以在extend方法的第一個參數傳入布爾值true。
```javascript
var o1 = {p1:['a','b']};
var o2 = $.extend({},o1);
var o3 = $.extend(true,{},o1);
o1.p1[0]='c';
o2.p1 // ["c", "b"]
o3.p1 // ["a", "b"]
```
上面代碼中,o2是淺拷貝,o3是深拷貝。結果,改變原始數組的屬性,o2會跟著一起變,而o3不會。
**(6)$.proxy**
$.proxy方法類似于ECMAScript 5的bind方法,可以綁定函數的上下文(也就是this對象)和參數,返回一個新函數。
jQuery.proxy()的主要用處是為回調函數綁定上下文對象。
```javascript
var o = {
type: "object",
test: function(event) {
console.log(this.type);
}
};
$("#button")
.on("click", o.test) // 無輸出
.on("click", $.proxy(o.test, o)) // object
```
上面的代碼中,第一個回調函數沒有綁定上下文,所以結果為空,沒有任何輸出;第二個回調函數將上下文綁定為對象o,結果就為object。
這個例子的另一種等價的寫法是:
```javascript
$("#button").on( "click", $.proxy(o, test))
```
上面代碼的$.proxy(o, test)的意思是,將o的方法test與o綁定。
這個例子表明,proxy方法的寫法主要有兩種。
```javascript
jQuery.proxy(function, context)
// or
jQuery.proxy(context, name)
```
第一種寫法是為函數(function)指定上下文對象(context),第二種寫法是指定上下文對象(context)和它的某個方法名(name)。
再看一個例子。正常情況下,下面代碼中的this對象指向發生click事件的DOM對象。
```javascript
$('#myElement').click(function() {
$(this).addClass('aNewClass');
});
```
如果我們想讓回調函數延遲運行,使用setTimeout方法,代碼就會出錯,因為setTimeout使得回調函數在全局環境運行,this將指向全局對象。
```javascript
$('#myElement').click(function() {
setTimeout(function() {
$(this).addClass('aNewClass');
}, 1000);
});
```
上面代碼中的this,將指向全局對象window,導致出錯。
這時,就可以用proxy方法,將this對象綁定到myElement對象。
```javascript
$('#myElement').click(function() {
setTimeout($.proxy(function() {
$(this).addClass('aNewClass');
}, this), 1000);
});
```
**(7)$.data,$.removeData**
$.data方法可以用來在DOM節點上儲存數據。
```javascript
// 存入數據
$.data(document.body, "foo", 52 );
// 讀取數據
$.data(document.body, "foo");
// 讀取所有數據
$.data(document.body);
```
上面代碼在網頁元素body上儲存了一個鍵值對,鍵名為“foo”,鍵值為52。
$.removeData方法用于移除$.data方法所儲存的數據。
```javascript
$.data(div, "test1", "VALUE-1");
$.removeData(div, "test1");
```
**(8)$.parseHTML,$.parseJSON,$.parseXML**
$.parseHTML方法用于將字符串解析為DOM對象。
$.parseJSON方法用于將JSON字符串解析為JavaScript對象,作用與原生的JSON.parse()類似。但是,jQuery沒有提供類似JSON.stringify()的方法,即不提供將JavaScript對象轉為JSON對象的方法。
$.parseXML方法用于將字符串解析為XML對象。
```javascript
var html = $.parseHTML("hello, <b>my name is</b> jQuery.");
var obj = $.parseJSON('{"name": "John"}');
var xml = "<rss version='2.0'><channel><title>RSS Title</title></channel></rss>";
var xmlDoc = $.parseXML(xml);
```
**(9)$.makeArray**
$.makeArray方法將一個類似數組的對象,轉化為真正的數組。
```javascript
var a = $.makeArray(document.getElementsByTagName("div"));
```
**(10)$.merge**
$.merge方法用于將一個數組(第二個參數)合并到另一個數組(第一個參數)之中。
```javascript
var a1 = [0,1,2];
var a2 = [2,3,4];
$.merge(a1, a2);
a1
// [0, 1, 2, 2, 3, 4]
```
**(11)$.now**
$.now方法返回當前時間距離1970年1月1日00:00:00 UTC對應的毫秒數,等同于(new Date).getTime()。
```javascript
$.now()
// 1388212221489
```
## 判斷數據類型的方法
jQuery提供一系列工具方法,用來判斷數據類型,以彌補JavaScript原生的typeof運算符的不足。以下方法對參數進行判斷,返回一個布爾值。
- jQuery.isArray():是否為數組。
- jQuery.isEmptyObject():是否為空對象(不含可枚舉的屬性)。
- jQuery.isFunction():是否為函數。
- jQuery.isNumeric():是否為數值(整數或浮點數)。
- jQuery.isPlainObject():是否為使用“{}”或“new Object”生成的對象,而不是瀏覽器原生提供的對象。
- jQuery.isWindow():是否為window對象。
- jQuery.isXMLDoc():判斷一個DOM節點是否處于XML文檔之中。
下面是一些例子。
```javascript
$.isEmptyObject({}) // true
$.isPlainObject(document.location) // false
$.isWindow(window) // true
$.isXMLDoc(document.body) // false
```
除了上面這些方法以外,還有一個$.type方法,可以返回一個變量的數據類型。它的實質是用Object.prototype.toString方法讀取對象內部的[[Class]]屬性(參見《標準庫》的Object對象一節)。
```javascript
$.type(/test/) // "regexp"
```
## Ajax操作
### $.ajax
jQuery對象上面還定義了Ajax方法($.ajax()),用來處理Ajax操作。調用該方法后,瀏覽器就會向服務器發出一個HTTP請求。
$.ajax()的用法主要有兩種。
```javascript
$.ajax(url[, options])
$.ajax([options])
```
上面代碼中的url,指的是服務器網址,options則是一個對象參數,設置Ajax請求的具體參數。
```javascript
$.ajax({
async: true,
url: '/url/to/json',
type: 'GET',
data : { id : 123 },
dataType: 'json',
timeout: 30000,
success: successCallback,
error: errorCallback,
complete: completeCallback,
statusCode: {
404: handler404,
500: handler500
}
})
function successCallback(json) {
$('<h1/>').text(json.title).appendTo('body');
}
function errorCallback(xhr, status){
console.log('出問題了!');
}
function completeCallback(xhr, status){
console.log('Ajax請求已結束。');
}
```
上面代碼的對象參數有多個屬性,含義如下:
- accepts:將本機所能處理的數據類型,告訴服務器。
- async:該項默認為true,如果設為false,則表示發出的是同步請求。
- beforeSend:指定發出請求前,所要調用的函數,通常用來對發出的數據進行修改。
- cache:該項默認為true,如果設為false,則瀏覽器不緩存返回服務器返回的數據。注意,瀏覽器本身就不會緩存POST請求返回的數據,所以即使設為false,也只對HEAD和GET請求有效。
- complete:指定當HTTP請求結束時(請求成功或請求失敗的回調函數,此時已經運行完畢)的回調函數。不管請求成功或失敗,該回調函數都會執行。它的參數為發出請求的原始對象以及返回的狀態信息。
- contentType:發送到服務器的數據類型。
- context:指定一個對象,作為所有Ajax相關的回調函數的this對象。
- crossDomain:該屬性設為true,將強制向相同域名發送一個跨域請求(比如JSONP)。
- data:向服務器發送的數據,如果使用GET方法,此項將轉為查詢字符串,附在網址的最后。
- dataType:向服務器請求的數據類型,可以設為text、html、script、json、jsonp和xml。
- error:請求失敗時的回調函數,函數參數為發出請求的原始對象以及返回的狀態信息。
- headers:指定HTTP請求的頭信息。
- ifModified:如果該屬性設為true,則只有當服務器端的內容與上次請求不一樣時,才會發出本次請求。
- jsonp:指定JSONP請求“callback=?”中的callback的名稱。
- jsonpCallback: 指定JSONP請求中回調函數的名稱。
- mimeType:指定HTTP請求的mime type。
- password:指定HTTP認證所需要的密碼。
- statusCode:值為一個對象,為服務器返回的狀態碼,指定特別的回調函數。
- success:請求成功時的回調函數,函數參數為服務器傳回的數據、狀態信息、發出請求的原始對象。
- timeout: 等待的最長毫秒數。如果過了這個時間,請求還沒有返回,則自動將請求狀態改為失敗。
- type:向服務器發送信息所使用的HTTP動詞,默認為GET,其他動詞有POST、PUT、DELETE。
- url:服務器端網址。這是唯一必需的一個屬性,其他屬性都可以省略。
- username:指定HTTP認證的用戶名。
- xhr:指定生成XMLHttpRequest對象時的回調函數。
這些參數之中,url可以獨立出來,作為ajax方法的第一個參數。也就是說,上面代碼還可以寫成下面這樣。
```javascript
$.ajax('/url/to/json',{
type: 'GET',
dataType: 'json',
success: successCallback,
error: errorCallback
})
```
作為向服務器發送的數據,data屬性也可以寫成一個對象。
```javascript
$.ajax({
url: '/remote/url',
data: {
param1: 'value1',
param2: 'value2',
...
}
});
// 相當于
$.ajax({
url: '/remote/url?param1=value1¶m2=value2...'
}});
```
### 簡便寫法
ajax方法還有一些簡便寫法。
- $.get():發出GET請求。
- $.getScript():讀取一個JavaScript腳本文件并執行。
- $.getJSON():發出GET請求,讀取一個JSON文件。
- $.post():發出POST請求。
- $.fn.load():讀取一個html文件,并將其放入當前元素之中。
一般來說,這些簡便方法依次接受三個參數:url、數據、成功時的回調函數。
**(1)$.get(),$.post()**
這兩個方法分別對應HTTP的GET方法和POST方法。
```javascript
$.get('/data/people.html', function(html){
$('#target').html(html);
});
$.post('/data/save', {name: 'Rebecca'}, function (resp){
console.log(JSON.parse(resp));
});
```
get方法和post方法的參數相同,第一個參數是服務器網址,該參數是必需的,其他參數都是可選的。第二個參數是發送給服務器的數據,第三個參數是操作成功后的回調函數。
上面的post方法對應的ajax寫法如下。
```javascript
$.ajax({
type: 'POST',
url: '/data/save',
data: {name: 'Rebecca'},
dataType: 'json',
success: function (resp){
console.log(JSON.parse(resp));
}
});
```
**(2)$.getJSON()**
ajax方法的另一個簡便寫法是getJSON方法。當服務器端返回JSON格式的數據,可以用這個方法代替$.ajax方法。
```javascript
$.getJSON('url/to/json', {'a': 1}, function(data){
console.log(data);
});
```
上面的代碼等同于下面的寫法。
```javascript
$.ajax({
dataType: "json",
url: '/url/to/data',
data: {'a': 1},
success: function(data){
console.log(data);
}
});
```
**(3)$.getScript()**
$.getScript方法用于從服務器端加載一個腳本文件。
```javascript
$.getScript('/static/js/myScript.js', function() {
functionFromMyScript();
});
```
上面代碼先從服務器加載myScript.js腳本,然后在回調函數中執行該腳本提供的函數。
getScript的回調函數接受三個參數,分別是腳本文件的內容,HTTP響應的狀態信息和ajax對象實例。
```javascript
$.getScript( "ajax/test.js", function (data, textStatus, jqxhr){
console.log( data ); // test.js的內容
console.log( textStatus ); // Success
console.log( jqxhr.status ); // 200
});
```
getScript是ajax方法的簡便寫法,因此返回的是一個deferred對象,可以使用deferred接口。
```javascript
jQuery.getScript("/path/to/myscript.js")
.done(function() {
// ...
})
.fail(function() {
// ...
});
```
**(4)$.fn.load()**
$.fn.load不是jQuery的工具方法,而是定義在jQuery對象實例上的方法,用于獲取服務器端的HTML文件,將其放入當前元素。由于該方法也屬于ajax操作,所以放在這里一起講。
```javascript
$('#newContent').load('/foo.html');
```
$.fn.load方法還可以指定一個選擇器,將遠程文件中匹配選擇器的部分,放入當前元素,并指定操作完成時的回調函數。
```javascript
$('#newContent').load('/foo.html #myDiv h1:first',
function(html) {
console.log('內容更新!');
});
```
上面代碼只加載foo.html中匹配“#myDiv h1:first”的部分,加載完成后會運行指定的回調函數。
```javascript
$('#main-menu a').click(function(event) {
event.preventDefault();
$('#main').load(this.href + ' #main *');
});
```
上面的代碼將指定網頁中匹配“#main *”,加載入當前的main元素。星號表示匹配main元素包含的所有子元素,如果不加這個星號,就會加載整個main元素(包括其本身),導致一個main元素中還有另一個main元素。
load方法可以附加一個字符串或對象作為參數,一起向服務器提交。如果是字符串,則采用GET方法提交;如果是對象,則采用POST方法提交。
```javascript
$( "#feeds" ).load( "feeds.php", { limit: 25 }, function() {
console.log( "已經載入" );
});
```
上面代碼將`{ limit: 25 }`通過POST方法向服務器提交。
load方法的回調函數,可以用來向用戶提示操作已經完成。
```javascript
$('#main-menu a').click(function(event) {
event.preventDefault();
$('#main').load(this.href + ' #main *', function(responseText, status) {
if (status === 'success') {
$('#notification-bar').text('加載成功!');
} else {
$('#notification-bar').text('出錯了!');
}
});
});
```
### Ajax事件
jQuery提供以下一些方法,用于指定特定的AJAX事件的回調函數。
- .ajaxComplete():ajax請求完成。
- .ajaxError():ajax請求出錯。
- .ajaxSend():ajax請求發出之前。
- .ajaxStart():第一個ajax請求開始發出,即沒有還未完成ajax請求。
- .ajaxStop():所有ajax請求完成之后。
- .ajaxSuccess():ajax請求成功之后。
下面是示例。
```javascript
$('#loading_indicator')
.ajaxStart(function (){$(this).show();})
.ajaxStop(function (){$(this).hide();});
```
下面是處理Ajax請求出錯(返回404或500錯誤)的例子。
```javascript
$(document).ajaxError(function (e, xhr, settings, error) {
console.log(error);
});
```
### 返回值
ajax方法返回的是一個deferred對象,可以用then方法為該對象指定回調函數(詳細解釋參見《deferred對象》一節)。
```javascript
$.ajax({
url: '/data/people.json',
dataType: 'json'
}).then(function (resp){
console.log(resp.people);
})
```
### JSONP
由于瀏覽器存在“同域限制”,ajax方法只能向當前網頁所在的域名發出HTTP請求。但是,通過在當前網頁中插入script元素(\<script\>),可以向不同的域名發出GET請求,這種變通方法叫做JSONP(JSON with Padding)。
ajax方法可以發出JSONP請求,方法是在對象參數中指定dataType為JSONP。
```javascript
$.ajax({
url: '/data/search.jsonp',
data: {q: 'a'},
dataType: 'jsonp',
success: function(resp) {
$('#target').html('Results: ' + resp.results.length);
}
});)
```
JSONP的通常做法是,在所要請求的URL后面加在回調函數的名稱。ajax方法規定,如果所請求的網址以類似“callback=?”的形式結尾,則自動采用JSONP形式。所以,上面的代碼還可以寫成下面這樣。
```javascript
$.getJSON('/data/search.jsonp?q=a&callback=?',
function(resp) {
$('#target').html('Results: ' + resp.results.length);
}
);
```
### 文件上傳
假定網頁有一個文件控件。
```html
<input type="file" id="test-input">
```
下面就是如何使用Ajax上傳文件。
```javascript
var file = $('#test-input')[0].files[0];
var formData = new FormData();
formData.append('file', file);
$.ajax('myserver/uploads', {
method: 'POST',
contentType: false,
processData: false,
data: formData
});
```
上面代碼是將文件作為表單數據發送。除此之外,也可以直接發送文件。
```javascript
var file = $('#test-input')[0].files[0];
$.ajax('myserver/uploads', {
method: 'POST',
contentType: file.type,
processData: false,
data: file
});
```
<h2 id="11.3">jQuery插件開發</h2>
所謂“插件”,就是用戶自己新增的jQuery實例對象的方法。由于該方法要被所有實例共享,所以只能定義在jQuery構造函數的原型對象(prototype)之上。對于用戶來說,把一些常用的操作封裝成插件(plugin),使用起來會非常方便。
## 插件的編寫
### 原理
本質上,jQuery插件是定義在jQuery構造函數的prototype對象上面的一個方法,這樣做就能使得所有jQuery對象的實例都能共享這個方法。因為jQuery構造函數的prototype對象被簡寫成jQuery.fn對象,所以插件采用下面的方法定義。
```javascript
jQuery.fn.myPlugin = function() {
// Do your awesome plugin stuff here
};
```
更好的做法是采用下面的寫法,這樣就能在函數體內自由使用美元符號($)。
```javascript
;(function ($){
$.fn.myPlugin = function (){
// Do your awesome plugin stuff here
};
})(jQuery);
```
上面代碼的最前面有一個分號,這是為了防止多個腳本文件合并時,其他腳本的結尾語句沒有添加分號,造成運行時錯誤。
有時,還可以把頂層對象(window)作為參數輸入,這樣可以加快代碼的執行速度和執行更有效的最小化操作。
```javascript
;(function ($, window) {
$.fn.myPlugin = function() {
// Do your awesome plugin stuff here
};
}(jQuery, window));
```
需要注意的是,在插件內部,this關鍵字指的是jQuery對象的實例。而在一般的jQuery回調函數之中,this關鍵字指的是DOM對象。
```javascript
(function ($){
$.fn.maxHeight = function (){
var max = 0;
// 下面這個this,指的是jQuery對象實例
this.each(function() {
// 回調函數內部,this指的是DOM對象
max = Math.max(max, $(this).height());
});
return max;
};
})(jQuery);
```
上面這個maxHeight插件的作用是,返回一系列DOM對象中高度最高的那個對象的高度。
大多數情況下,插件應該返回jQuery對象,這樣可以保持鏈式操作。
```javascript
(function ($){
$.fn.greenify = function (){
this.css("color", "green");
return this;
};
})(jQuery);
$("a").greenify().addClass("greenified");
```
上面代碼返回this對象,即jQuery對象實例,所以接下來可以采用鏈式操作。
對于包含多個jQuery對象的結果集,可以采用each方法,進行處理。
```javascript
$.fn.myNewPlugin = function() {
return this.each(function() {
// 處理每個對象
});
};
```
插件可以接受一個屬性對象參數。
```javascript
(function ($){
$.fn.tooltip = function (options){
var settings = $.extend( {
'location' : 'top',
'background-color' : 'blue'
}, options);
return this.each(function (){
// 填入插件代碼
});
};
})(jQuery);
```
上面代碼使用extend方法,為參數對象設置屬性的默認值。
### 偵測環境
jQuery逐漸從瀏覽器環境,變為也可以用于服務器環境。所以,定義插件的時候,最好首先偵測一下運行環境。
```javascript
if (typeof module === "object" && typeof module.exports === "object") {
// CommonJS版本
} else {
// 瀏覽器版本
}
```
## 實例
下面是一個將a元素的href屬性添加到網頁的插件。
```javascript
(function($){
$.fn.showLinkLocation = function() {
return this.filter('a').append(function(){
return ' (' + this.href + ')';
});
};
}(jQuery));
// 用法
$('a').showLinkLocation();
```
從上面的代碼可以看到,插件的開發和使用都非常簡單。
## 插件的發布
編寫插件以后,可以將它發布到[jQuery官方網站](http://plugins.jquery.com/)上。
首先,編寫一個插件的信息文件yourPluginName.jquery.json。文件名中的yourPluginName表示你的插件名。
```javascript
{
"name": "plugin_name",
"title": "plugin_long_title",
"description": "...",
"keywords": ["jquery", "plugins"],
"version": "0.0.1",
"author": {
"name": "...",
"url": "..."
},
"maintainers": [
{
"name": "...",
"url": "..."
}
],
"licenses": [
{
"type": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
],
"bugs": "...", // bugs url
"homepage": "...", // homepage url
"docs": "...", // docs url
"download": "...", // download url
"dependencies": {
"jquery": ">=1.4"
}
}
```
上面是一個插件信息文件的實例。
然后,將代碼文件發布到Github,在設置頁面點擊“Service Hooks/WebHook URLs”選項,填入網址http://plugins.jquery.com/postreceive-hook,再點擊“Update Settings”進行保存。
最后,為代碼加上版本,push到github,你的插件就會加入jQuery官方插件庫。
```javascript
git tag 0.1.0
git push origin --tags
```
以后,你要發布新版本,就做一個新的tag。
<h2 id="11.4">jQuery.Deferred對象</h2>
## 概述
deferred對象代表了將要完成的某種操作,并提供了一些方法,幫助用戶使用。它是jQuery對Promises接口的實現。jQuery的所有Ajax操作函數,默認返回的就是一個deferred對象。
簡單說,Promises是異步操作的通用接口,扮演代理人(proxy)的角色,將異步操作包裝成具有同步操作特性的特殊對象。異步操作的典型例子就是Ajax操作、網頁動畫、web worker等等。
由于JavaScript單線程的特點,如果某個操作耗時很長,其他操作就必需排隊等待。為了避免整個程序失去響應,通常的解決方法是將那些排在后面的操作,寫成“回調函數”(callback)的形式。這樣做雖然可以解決問題,但是有一些顯著缺點:
- 回調函數往往寫成函數參數的形式,形成所謂的“持續傳遞風格”(即參數就是下一步操作,Continuation-passing style),導致函數的輸入和輸出非常混亂,整個程序的可閱讀性差;
- 回調函數往往只能指定一個,如果有多個操作,就需要改寫回調函數。
- 除了正常的報錯機制,錯誤還可能通過回調函數的形式返回,增加了除錯和調試的難度。
- 正常的函數輸入和輸出可以區分得很清楚,回調函數使得函數的輸出不再重要。
Promises就是為了解決這些問題而提出的,它的主要目的就是取代回調函數,成為非同步操作的解決方案。它的核心思想就是讓非同步操作返回一個對象,其他操作都針對這個對象來完成。比如,假定ajax操作返回一個Promise對象。
```javascript
var promise = get('http://www.example.com');
```
然后,Promise對象有一個then方法,可以用來指定回調函數。一旦非同步操作完成,就調用指定的回調函數。
```javascript
promise.then(function (content) {
console.log(content)
})
```
可以將上面兩段代碼合并起來,這樣程序的流程看得更清楚。
```javascript
get('http://www.example.com').then(function (content) {
console.log(content)
})
```
在1.7版之前,jQuery的Ajax操作采用回調函數。
```javascript
$.ajax({
url:"/echo/json/",
success: function(response)
{
console.info(response.name);
}
});
```
1.7版之后,Ajax操作直接返回Promise對象,這意味著可以用then方法指定回調函數。
```javascript
$.ajax({
url: "/echo/json/",
}).then(function (response) {
console.info(response.name);
});
```
## deferred對象的方法
### 基本用法
**(1)生成deferred對象**
第一步是通過$.Deferred()方法,生成一個deferred對象。
```javascript
var deferred = $.Deferred();
```
**(2)deferred對象的狀態**
deferred對象有三種狀態。
- pending:表示操作還沒有完成。
- resolved:表示操作成功。
- rejected:表示操作失敗。
state方法用來返回deferred對象當前狀態。
```javascript
$.Deferred().state() // 'pending'
$.Deferred().resolve().state() // 'resolved'
$.Deferred().reject().state() // 'rejected'
```
**(3)改變狀態的方法**
resolve方法將deferred對象的狀態從pending改為resolved,reject方法則將狀態從pending改為rejected。
```javascript
var deferred = $.Deferred();
deferred.resolve("hello world");
```
resolve方法的參數,用來傳遞給回調函數。
**(4)綁定回調函數**
deferred對象在狀態改變時,會觸發回調函數。
done方法指定狀態變為resolved(操作成功)時的回調函數;fail方法指定狀態變為rejected(操作失敗)時的回調函數;always方法指定,不管狀態變為resolved或rejected,都會觸發的方法。
```javascript
var deferred = $.Deferred();
deferred.done(function(value) {
console.log(value);
}).resolve('hello world');
// hello world
```
上述三種方法都返回的原有的deferred對象,因此可以采用鏈式寫法,在后面再鏈接別的方法(包括done和fail在內)。
```javascript
$.Deferred().done(f1).fail(f2).always(f3);
```
### notify() 和 progress()
progress()用來指定一個回調函數,當調用notify()方法時,該回調函數將執行。它的用意是提供一個接口,使得在非同步操作執行過程中,可以執行某些操作,比如定期返回進度條的進度。
```javascript
var userProgress = $.Deferred();
var $profileFields = $("input");
var totalFields = $profileFields.length
userProgress.progress(function (filledFields) {
var pctComplete = (filledFields/totalFields)*100;
$("#progress").html(pctComplete.toFixed(0));
});
userProgress.done(function () {
$("#thanks").html("Thanks for completing your profile!").show();
});
$("input").on("change", function () {
var filledFields = $profileFields.filter("[value!='']").length;
userProgress.notify(filledFields);
if (filledFields == totalFields) {
userProgress.resolve();
}
});
```
### then方法
**(1)概述**
then方法的作用也是指定回調函數,它可以接受三個參數,也就是三個回調函數。第一個參數是resolve時調用的回調函數(相當于done方法),第二個參數是reject時調用的回調函數(相當于fail方法),第三個參數是progress()方法調用的回調函數。
```javascript
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
```
**(2)返回值**
在jQuery 1.8之前,then()只是.done().fail()寫法的語法糖,兩種寫法是等價的。在jQuery 1.8之后,then()返回一個新的promise對象,而done()返回的是原有的deferred對象。如果then()指定的回調函數有返回值,該返回值會作為參數,傳入后面的回調函數。
```javascript
var defer = jQuery.Deferred();
defer.done(function(a,b){
return a * b;
}).done(function( result ) {
console.log("result = " + result);
}).then(function( a, b ) {
return a * b;
}).done(function( result ) {
console.log("result = " + result);
}).then(function( a, b ) {
return a * b;
}).done(function( result ) {
console.log("result = " + result);
});
defer.resolve( 2, 3 );
```
在jQuery 1.8版本之前,上面代碼的結果是:
```javascript
result = 2
result = 2
result = 2
```
在jQuery 1.8版本之后,返回結果是
```javascript
result = 2
result = 6
result = NaN
```
這一點需要特別引起注意。
```javascript
$.ajax( url1, { dataType: "json" } )
.then(function( data ) {
return $.ajax( url2, { data: { user: data.userId } } );
}).done(function( data ) {
// 從url2獲取的數據
});
```
上面代碼最后那個done方法,處理的是從url2獲取的數據,而不是從url1獲取的數據。
**(3)對返回值的修改**
利用then()會修改返回值這個特性,我們可以在調用其他回調函數之前,對前一步操作返回的值進行處理。
```javascript
var post = $.post("/echo/json/")
.then(function(p){
return p.firstName;
});
post.done(function(r){ console.log(r); });
```
上面代碼先使用then()方法,從返回的數據中取出所需要的字段(firstName),所以后面的操作就可以只處理這個字段了。
有時,Ajax操作返回json字符串里面有一個error屬性,表示發生錯誤。這個時候,傳統的方法只能是通過done()來判斷是否發生錯誤。通過then()方法,可以讓deferred對象調用fail()方法。
```javascript
var myDeferred = $.post('/echo/json/', {json:JSON.stringify({'error':true})})
.then(function (response) {
if (response.error) {
return $.Deferred().reject(response);
}
return response;
},function () {
return $.Deferred().reject({error:true});
}
);
myDeferred.done(function (response) {
$("#status").html("Success!");
}).fail(function (response) {
$("#status").html("An error occurred");
});
```
上面代碼中,不管是通信出錯,或者服務器返回一個錯誤,都會調用reject方法,返回一個新的deferred對象,狀態為rejected,因此就會觸發fail方法指定的回調函數。
關于error的處理,jQuery的deferred對象與其他實現Promises規范的函數庫有一個重大不同。就是說,如果deferred對象執行過程中,拋出一個非Promises對象的錯誤,那么將不會被后繼的then方法指定的rejected回調函數捕獲,而會一直傳播到應用程序層面。為了代碼行為與Promises規范保持一致,建議出錯時,總是使用reject方法返回錯誤。
```javascript
d = $.Deferred()
d.then(function(){
throw new Error('err')
}).fail(function(){
console.log('fail')
})
d.resolve()
// Error: err
```
上面代碼中,then的回調函數拋出一個錯誤,按照Promises規范,應該被fail方法的回調函數捕獲,但是jQuery的部署是上升到應用程序的層面。
**(4)回調函數的返回值**
如果回調函數返回deferred對象,則then方法的返回值將是對應這個返回值的promise對象。
```javascript
var d1 = $.Deferred();
var promise = $.when('Hello').then(function(h){
return $.when(h,d1);
})
promise.done(function (s1,s2) {
console.log(s1);
console.log(s2);
})
d1.resolve('World')
// Hello
// World
```
上面代碼中,done方法的回調函數,正常情況下只能接受一個參數。但是由于then方法的回調函數,返回一個when方法生成的deferred對象,導致它可以接受兩個參數。
### pipe方法
pipe方法接受一個函數作為參數,表示在調用then方法、done方法、fail方法、always方法指定的回調函數之前,先運行pipe方法指定的回調函數。它通常用來對服務器返回的數據做初步處理。
### 與Promise A+規格的差異
Promise事實上的標準是社區提出的Promise A+規格,jQuery的實現并不完全符合Promise A+,主要是對錯誤的處理。
```javascript
var promise2 = promise1.then(function () {
throw new Error("boom!");
});
```
上面代碼在回調函數中拋出一個錯誤,Promise A+規定此時Promise實例的狀態變為reject,該錯誤被下一個catch方法指定的回調函數捕獲。但是,jQuery的Deferred對象此時不會改變狀態,亦不會觸發回調函數,該錯誤一般情況下會被window.onerror捕獲。換句話說,在Deferred對象中,總是必須使用reject方法來改變狀態。
## promise對象
**(1)概念**
一般情況下,從外部改變第三方完成的異步操作(比如Ajax)的狀態是毫無意義的。為了防止用戶這樣做,可以在deferred對象的基礎上,返回一個針對它的promise對象。
簡單說,promise對象就是不能改變狀態的deferred對象,也就是deferred的只讀版。或者更通俗地理解成,promise是一個對將要完成的任務的承諾,排除了其他人破壞這個承諾的可能性,只能等待承諾方給出結果。
你可以通過promise對象,為原始的deferred對象添加回調函數,查詢它的狀態,但是無法改變它的狀態,也就是說promise對象不允許你調用resolve和reject方法。
**(2)生成promise對象**
deferred對象的promise方法,用來生成對應的promise對象。
```javascript
function getPromise(){
return $.Deferred().promise();
}
try{
getPromise().resolve("a");
} catch(err) {
console.log(err);
}
// TypeError
```
上面代碼對promise對象,調用resolve方法,結果報錯。
jQuery的`ajax()`方法返回的就是一個Promise對象。此外,Animation類操作也可以使用`promise`方法。
```javascript
$('body').toggle('blinds').promise().then(
function(){
$('body').toggle('blinds')
}
)
```
## 輔助方法
deferred對象還有一系列輔助方法,使它更方便使用。
### `$.when()`方法
`$.when()`接受多個deferred對象作為參數,當它們全部運行成功后,才調用resolved狀態的回調函數,但只要其中有一個失敗,就調用rejected狀態的回調函數。它相當于將多個非同步操作,合并成一個。實質上,when方法為多個deferred對象,返回一個單一的promise對象。
```javascript
$.when(
$.ajax( "/main.php" ),
$.ajax( "/modules.php" ),
$.ajax( "/lists.php" )
).then(successFunc, failureFunc);
```
上面代碼表示,要等到三個ajax操作都結束以后,才執行then方法指定的回調函數。
when方法里面要執行多少個操作,回調函數就有多少個參數,對應前面每一個操作的返回結果。
```javascript
$.when(
$.ajax( "/main.php" ),
$.ajax( "/modules.php" ),
$.ajax( "/lists.php" )
).then(function (resp1, resp2, resp3){
console.log(resp1);
console.log(resp2);
console.log(resp3);
});
```
上面代碼的回調函數有三個參數,resp1、resp2和resp3,依次對應前面三個ajax操作的返回結果。
如果when方法的參數不是deferred或promise對象,則直接作為回調函數的參數。
```javascript
d = $.Deferred()
$.when(d, 'World').done(function (s1, s2){
console.log(s1);
console.log(s2);
})
d.resolve('Hello')
// Hello
// World
```
上面代碼中,when的第二個參數是一個字符串,則直接作為回調函數的第二個參數。
此外,如果when方法的參數都不是deferred或promise對象,那么when方法的回調函數將立即運行。
## 使用實例
### wait方法
我們可以用deferred對象寫一個wait方法,表示等待多少毫秒后再執行。
```javascript
$.wait = function(time) {
return $.Deferred(function(dfd) {
setTimeout(dfd.resolve, time);
});
}
```
使用方法如下。
```javascript
$.wait(5000).then(function() {
console.log("Hello from the future!");
});
```
### 改寫setTimeout
在上面的wait方法的基礎上,還可以改寫setTimeout方法,讓其返回一個deferred對象。
```javascript
function doSomethingLater(fn, time) {
var dfd = $.Deferred();
setTimeout(function() {
dfd.resolve(fn());
}, time || 0);
return dfd.promise();
}
var promise = doSomethingLater(function (){
console.log( '已經延遲執行' );
}, 100);
```
### 自定義操作使用deferred接口
我們可以利用deferred接口,使得任意操作都可以用done()和fail()指定回調函數。
```javascript
Twitter = {
search:function(query) {
var dfd = $.Deferred();
$.ajax({
url:"http://search.twitter.com/search.json",
data:{q:query},
dataType:'jsonp',
success:dfd.resolve
});
return dfd.promise();
}
}
```
使用方法如下。
```javascript
Twitter.search('javaScript').then(function(data) {
alert(data.results[0].text);
});
```
deferred對象的另一個優勢是可以附加多個回調函數。下面的例子使用了上面所改寫的setTimeout函數。
```javascript
function doSomething(arg) {
var dfd = $.Deferred();
setTimeout(function() {
dfd.reject("Sorry, something went wrong.");
});
return dfd;
}
doSomething("uh oh").done(function() {
console.log("Won't happen, we're erroring here!");
}).fail(function(message) {
console.log(message);
});
```
<h2 id="11.5">如何做到 jQuery-free?</h2>
## 概述
jQuery是最流行的JavaScript工具庫。據[統計](http://w3techs.com/technologies/details/js-jquery/all/all),目前全世界57.3%的網站使用它。也就是說,10個網站里面,有6個使用jQuery。如果只考察使用工具庫的網站,這個比例就會上升到驚人的91.7%。
jQuery如此受歡迎,以至于有被濫用的趨勢。許多開發者不管什么樣的項目,都一股腦使用jQuery。但是,jQuery本質只是一個中間層,提供一套統一易用的DOM操作接口,消除瀏覽器之間的差異。多了這一層中間層,操作的性能和效率多多少少會打一些折扣。
2006年,jQuery誕生的時候,主要是為了解決IE6與標準的不兼容問題。如今的[情況](http://en.wikipedia.org/wiki/Usage_share_of_web_browsers)已經發生了很大的變化。IE的市場份額不斷下降,以ECMAScript為基礎的JavaScript標準語法,正得到越來越廣泛的支持,不同瀏覽器對標準的支持越來越好、越來越趨同。開發者直接使用JavaScript標準語法,就能同時在各大瀏覽器運行,不再需要通過jQuery獲取兼容性。
另一方面,jQuery臃腫的[體積](http://mathiasbynens.be/demo/jquery-size)也讓人頭痛不已。jQuery 2.0的原始大小為235KB,優化后為81KB;如果是支持IE6、7、8的jQuery 1.8.3,原始大小為261KB,優化后為91KB。即使有CDN,瀏覽器加載這樣大小的腳本,也會產生不小的開銷。
所以,對于一些不需要支持老式瀏覽器的小型項目來說,不使用jQuery,直接使用DOM原生接口,可能是更好的選擇。開發者有必要了解,jQuery的一些常用操作所對應的DOM寫法。而且,理解jQuery背后的原理,會幫助你更好地使用jQuery。要知道有一種極端的說法是,如果你不理解一樣東西,就不要使用它。
下面就探討如何用JavaScript標準語法,取代jQuery的一些主要功能,做到jQuery-free。
## 選取DOM元素
jQuery的核心是通過各種選擇器,選中DOM元素,可以用querySelectorAll方法模擬這個功能。
```javascript
var $ = document.querySelectorAll.bind(document);
```
這里需要注意的是,querySelectorAll方法返回的是NodeList對象,它很像數組(有數字索引和length屬性),但不是數組,不能使用pop、push等數組特有方法。如果有需要,可以考慮將Nodelist對象轉為數組。
```javascript
myList = Array.prototype.slice.call(myNodeList);
```
## DOM操作
DOM本身就具有很豐富的操作方法,可以取代jQuery提供的操作方法。
獲取父元素。
```javascript
// jQuery寫法
$("#elementID").parent()
// DOM寫法
document.getElementById("elementID").parentNode
```
獲取下一個同級元素。
```javascript
// jQuery寫法
$("#elementID").next()
// DOM寫法
document.getElementById("elementID").nextSibling
```
尾部追加DOM元素。
```javascript
// jQuery寫法
$(parent).append($(child));
// DOM寫法
parent.appendChild(child)
```
頭部插入DOM元素。
```javascript
// jQuery寫法
$(parent).prepend($(child));
// DOM寫法
parent.insertBefore(child, parent.childNodes[0])
```
生成DOM元素。
```javascript
// jQuery寫法
$("<p>")
// DOM寫法
document.createElement("p")
```
刪除DOM元素。
```javascript
// jQuery寫法
$(child).remove()
// DOM寫法
child.parentNode.removeChild(child)
```
清空子元素。
```javascript
// jQuery寫法
$("#elementID").empty()
// DOM寫法
var element = document.getElementById("elementID");
while(element.firstChild) element.removeChild(element.firstChild);
```
檢查是否有子元素。
```javascript
// jQuery寫法
if (!$("#elementID").is(":empty")){}
// DOM寫法
if (document.getElementById("elementID").hasChildNodes()){}
```
克隆元素。
```javascript
// jQuery寫法
$("#elementID").clone()
// DOM寫法
document.getElementById("elementID").cloned(true)
```
## 事件的監聽
jQuery使用on方法,監聽事件和綁定回調函數。
```javascript
$('button').on('click', function(){
ajax( ... );
});
```
完全可以自己定義on方法,將它指向addEventListener方法。
```javascript
Element.prototype.on = Element.prototype.addEventListener;
```
為了使用方便,可以在NodeList對象上也部署這個方法。
```javascript
NodeList.prototype.on = function (event, fn) {
[]['forEach'].call(this, function (el) {
el.on(event, fn);
});
return this;
};
```
取消事件綁定的off方法,也可以自己定義。
```javascript
Element.prototype.off = Element.prototype.removeEventListener;
```
## 事件的觸發
jQuery的trigger方法則需要單獨部署,相對復雜一些。
```javascript
Element.prototype.trigger = function (type, data) {
var event = document.createEvent('HTMLEvents');
event.initEvent(type, true, true);
event.data = data || {};
event.eventName = type;
event.target = this;
this.dispatchEvent(event);
return this;
};
```
在NodeList對象上也部署這個方法。
```javascript
NodeList.prototype.trigger = function (event) {
[]['forEach'].call(this, function (el) {
el['trigger'](event);
});
return this;
};
```
## `$(document).ready`
DOM加載完成,會觸發DOMContentLoaded事件,等同于jQuery的`$(document).ready`方法。
```javascript
document.addEventListener("DOMContentLoaded", function() {
// ...
});
```
不過,目前的最佳實踐,是將JavaScript腳本文件都放在頁面底部加載。這樣的話,其實$(document).ready方法(可以簡寫為$(function))已經不必要了,因為等到運行的時候,DOM對象已經生成了。
## attr方法
jQuery使用attr方法,讀寫網頁元素的屬性。
```javascript
$("#picture").attr("src", "http://url/to/image")
```
DOM提供getAttribute和setAttribute方法讀寫元素屬性。
```javascript
imgElement.setAttribute("src", "http://url/to/image")
```
DOM還允許直接讀取屬性值,寫法要簡潔許多。
```javascript
imgElement.src = "http://url/to/image";
```
> 需要注意的是,文本框元素(input)的this.value返回的是輸入框中的值,鏈接元素(a標簽)的this.href返回的是絕對URL。如果需要用到這兩個網頁元素的屬性準確值,可以用this.getAttribute('value')和this.getAttibute('href')。
## addClass方法
jQuery的addClass方法,用于為DOM元素添加一個class。
```javascript
$('body').addClass('hasJS');
```
DOM元素本身有一個可讀寫的className屬性,可以用來操作class。
```javascript
document.body.className = 'hasJS';
// or
document.body.className += ' hasJS';
```
HTML 5還提供一個classList對象,功能更強大(IE 9不支持)。
```javascript
document.body.classList.add('hasJS');
document.body.classList.remove('hasJS');
document.body.classList.toggle('hasJS');
document.body.classList.contains('hasJS');
```
## CSS
jQuery的css方法,用來設置網頁元素的樣式。
```javascript
$(node).css( "color", "red" );
```
DOM元素有一個style屬性,可以直接操作。
```javascript
element.style.color = "red”;;
// or
element.style.cssText += 'color:red';
```
## 數據儲存
jQuery對象可以儲存數據。
```javascript
$("body").data("foo", 52);
```
HTML 5有一個dataset對象,也有類似的功能(IE 10不支持),不過只能保存字符串。
```javascript
element.dataset.user = JSON.stringify(user);
element.dataset.score = score;
```
## Ajax
jQuery的ajax方法,用于異步操作。
```javascript
$.ajax({
type: "POST",
url: "some.php",
data: { name: "John", location: "Boston" }
}).done(function( msg ) {
alert( "Data Saved: " + msg );
});
```
我們自定義一個ajax函數,簡單模擬jQuery的ajax方法。
```javascript
function ajax(url, opts){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
var completed = 4;
if(xhr.readyState === completed){
if(xhr.status === 200){
opts.success(xhr.responseText, xhr);
}else{
opts.error(xhr.responseText, xhr);
}
}
};
xhr.open(opts.method, url, true);
xhr.send(opts.data);
}
```
使用的時候,除了網址,還需要傳入一個自己構造的option對象。
```javascript
ajax('/foo', {
method: 'GET',
success: function(response){
console.log(response);
},
error: function(response){
console.log(response);
}
});
```
## 動畫
jQuery的animate方法,用于生成動畫效果。
```javascript
$foo.animate('slow', { x: '+=10px' })
```
jQuery的動畫效果,很大部分基于DOM。但是目前,CSS 3的動畫遠比DOM強大,所以可以把動畫效果寫進CSS,然后通過操作DOM元素的class,來展示動畫。
```javascript
foo.classList.add('animate')
```
如果需要對動畫使用回調函數,CSS 3也定義了相應的事件。
```javascript
el.addEventListener("webkitTransitionEnd", transitionEnded);
el.addEventListener("transitionend", transitionEnded);
```
## 替代方案
由于jQuery體積過大,替代方案層出不窮。
其中,最有名的是[zepto.js](http://zeptojs.com/)。它的設計目標是以最小的體積,做到最大兼容jQuery的API。它的1.0版的原始大小是55KB,優化后是29KB,gzip壓縮后為10KB。
如果不求最大兼容,只希望模擬jQuery的基本功能。那么,[min.js](https://github.com/remy/min.js)優化后只有200字節,而[dolla](https://github.com/lelandrichardson/dolla)優化后是1.7KB。
此外,jQuery本身也采用模塊設計,可以只選擇使用自己需要的模塊。具體做法參見jQuery的[github網站](https://github.com/jquery/jquery),或者使用專用的[Web界面](http://projects.jga.me/jquery-builder/)。