💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
<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事件的回调函数。也就是说,当页面解析完成(即下载完&lt;/html&gt;标签)以后,在所有外部资源(图片、脚本等)完成加载之前,该函数就会立刻运行。 ```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&param2=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/)。