《jQuery基础教程(第四版)》学习笔记
本书代码参考: Learning jQuery Code Listing Browser
原书: jQuery基础教程
目录:
1. 使用$()函数
$()函数其实是创建了一个jQuery对象. 这个函数接受CSS选择符作为参数,充当一个工厂, 返回包含页面中对应元素的jQuery对象. 所有能在样式表中使用的选择符都可以传给这个函数, 随后就可以对匹配的元素集合应用jQuery方法.
在jQuery中,美元符号$其实就是标示符jQuery的"别名".
2. 选择符
1. 基本选择符 $('p') //取得所有标签为p的元素 $('.class') //取得所有类为class的元素 $('#id') //取得id为id的元素 //以及其他css中的选择符 2. 属性选择符 $('img[alt]') //选择带有alt属性的所有图像元素 //^表示值在字符串的开始, $表示值在字符串的结尾. *表示要匹配的值可以出现在字符串的任意位置, !表示对值取反 $('a[href^="mailto:"]') //选择页面中所有mailto:链接 $('a[href$=".pdf"]') //选择页面中所有pdf文档链接 $('a[href^="http"][href*="henry"]') //选择href属性以http开头且在任意位置包含henry的元素 3. 自定义选择符 $('div.horizontal:eq(1)') //取得集合中的第二个元素 $('tr:even') //选择奇数行. 之所以是奇数行是因为第一行的编号是0 还可以写作: $('tr').filter(':even')
$('tr:nth-child(odd)') //选择奇数行. :nth-child()是jq中唯一从1开始计数的选择符 $('tr:contains(Henry)') //根据上下文内容选择元素. contains选择符区分大小写 4. 基于表单的选择符 $('input[type="radio"]:checked') //可以选择所有选中的单选按钮 $('input[type="text"]:disabled') //选择禁用的文本输入字段
更多的基于表单的选择符
:input
:button
:enabled
:disabled
:checked:selected
3. DOM遍历方法
filter方法
$('tr').filter(':even') $('a').filter(function(){ return this.hostname && this.hostname!=location.hostname; }) //选择包含带有域名的href属性的,且域名不等于页面当前所在域的名称的a元素
.next() //选择下一个最接近的同辈元素 $('td:contains(Henry)').next() .nextAll() //选择后面的全部同辈元素 //对应方法 .prev() .prevAll() .siblings() //选择处于相同DOM层次的所有其他元素,无论这些元素处于当前元素之前还是之后 .parent().children() //另一种方法
连缀方法的原理:
几乎所有的jQuery方法都会返回一个jQuery对象, 因而可连缀调用多个jQuery方法.
4. 访问DOM元素
.get() var myTag = $('#my-element').get(0).tagName; //获取带有id为my-element属性的元素的标签名 var myTag = $('my-element')[0].tagName; //一种简写方式
函数 | 描述 |
---|---|
.add() | 将元素添加到匹配元素的集合中。 |
.andSelf() | 把堆栈中之前的元素集添加到当前集合中。 |
.children() | 获得匹配元素集合中每个元素的所有子元素。 |
.closest() | 从元素本身开始,逐级向上级元素匹配,并返回最先匹配的祖先元素。 |
.contents() | 获得匹配元素集合中每个元素的子元素,包括文本和注释节点。 |
.each() | 对 jQuery 对象进行迭代,为每个匹配元素执行函数。 |
.end() | 结束当前链中最近的一次筛选操作,并将匹配元素集合返回到前一次的状态。 |
.eq() | 将匹配元素集合缩减为位于指定索引的新元素。 |
.filter() | 将匹配元素集合缩减为匹配选择器或匹配函数返回值的新元素。 |
.find() | 获得当前匹配元素集合中每个元素的后代,由选择器进行筛选。 |
.first() | 将匹配元素集合缩减为集合中的第一个元素。 |
.has() | 将匹配元素集合缩减为包含特定元素的后代的集合。 |
.is() | 根据选择器检查当前匹配元素集合,如果存在至少一个匹配元素,则返回 true。 |
.last() | 将匹配元素集合缩减为集合中的最后一个元素。 |
.map() | 把当前匹配集合中的每个元素传递给函数,产生包含返回值的新 jQuery 对象。 |
.next() | 获得匹配元素集合中每个元素紧邻的同辈元素。 |
.nextAll() | 获得匹配元素集合中每个元素之后的所有同辈元素,由选择器进行筛选(可选)。 |
.nextUntil() | 获得每个元素之后所有的同辈元素,直到遇到匹配选择器的元素为止。 |
.not() | 从匹配元素集合中删除元素。 |
.offsetParent() | 获得用于定位的第一个父元素。 |
.parent() | 获得当前匹配元素集合中每个元素的父元素,由选择器筛选(可选)。 |
.parents() | 获得当前匹配元素集合中每个元素的祖先元素,由选择器筛选(可选)。 |
.parentsUntil() | 获得当前匹配元素集合中每个元素的祖先元素,直到遇到匹配选择器的元素为止。 |
.prev() | 获得匹配元素集合中每个元素紧邻的前一个同辈元素,由选择器筛选(可选)。 |
.prevAll() | 获得匹配元素集合中每个元素之前的所有同辈元素,由选择器进行筛选(可选)。 |
.prevUntil() | 获得每个元素之前所有的同辈元素,直到遇到匹配选择器的元素为止。 |
.siblings() | 获得匹配元素集合中所有元素的同辈元素,由选择器筛选(可选)。 |
.slice() | 将匹配元素集合缩减为指定范围的子集。 |
参考: jQuery 参考手册 - 遍历
第3章 事件
1. $(document).ready()和window.onload事件
- $(document).ready()注册是事件处理程序会在DOM完全就绪并可以使用时调用.这意味着所有元素对脚本而言都是可以访问的,但是并不意味着所有关联的文件都已经下载完毕. 即当HTML下载完成并解析成DOM树后,代码就可以运行.
- 而window.onload意味着文档完全下载到浏览器中了.
- 一般来说,使用$(document).ready()要优于onload. 但是因为支持文件可能还没有加载完成,所以类似图像的高度和宽度这样的属性不一定有效. 如果需要访问这些属性, 可能需要选择实现一个onload事件处理程序, 使这两种机制能够和平共存.
$(document).ready()可简写为: $(); 即:
$(function(){ //code here });
为了避免$的冲突,可以设置出让$标示符控制权
jQuery.noConflict(); //之和就通过jQuery来调用jQuery的方法
这种情况下,可以在.ready()函数中使用$的技巧----传递给ready函数的回调函数可以接收一个参数----jQuery对象本身,利用这个参数,可以重新命名jQuery为$,而不必担心造成冲突
jQuery(function($){ //使用$的代码 });
2. 事件添加
//.on绑定事件 $(this).on('click',function(){}); //.click $(this).click(function(){}) //添加类 $('btn').addClass('classname'); //移除类 $('btn').removeClass('classname'); //根据相应的类是否存在而添加或删除类 $('btn').toggleClass('classname');
.hover()方法: 接受两个函数参数,第一个会在鼠标指针进入被选择的元素时执行,而第二个函数会在鼠标指针离开该元素时触法
//设定事件目标 $('#switcher').click(function(event){ if(event.target = this){ $('#switcher button').toggleClass('hidden'); } }); //停止事件传播 event.stopPropagation(); //避免其他DOM元素响应事件 //阻止默认操作 .preventDefault();
同时调用.stopPropagation()和preventDefault()的一种简写方式: 在事件处理程序中返回false
3. 事件委托:
$(function(){ $('#switcher').click(function(event){ if($(event.target).is('button')){ //.is()方法接收一个选择符表达式,然后用选择符来测试当前jQuery对象. 如果集合中至少有一个元素与选择符匹配,.is()返回true var bodyClass = event.target.id.split('-')[1]; $('body').removeClass().addClass(bodyClass); $('#switcher button').removeClass('selected'); $(event.target).addClass('selected'); //这里this引用的都是"#switch"对象,因此要访问被单击的按钮斗殴要通过event.target来引用 event.stopPropagation(); } }); });
.on()方法也可以接收相应参数实现事件委托. 如果给.on()方法传入的第二个参数是一个选择符表达式,jQuery会把click事件处理程序绑定到#switch对象,同时比较event.target和选择符表达式. 如果匹配, jQuery会把this关键字映射到匹配的元素, 否则不会执行事件处理程序.
4. 移除事件处理
.off('click')
还可以为事件处理程序添加命名空间
$('#switcher').on('click.collapse', function(){}); $('#switcher').off('click.collapse');
对于事件处理系统而言, 后缀.collapse是不可见的.
如果不使用匿名函数,则就可以不使用事件命名空间, .off()会把命名函数作为第二个参数, 结果只会解除对特定处理程序的棒的.
第4章 样式与动画
1. 通过.css()来获取或修改元素样式属性的值.
为了获取某个样式属性的值, 可以为这个方法传递一个字符串形式的属性名, 然后同样得到一个字符串形式的属性值. 要取得多个样式属性的值, 可以传入属性名的数组, 得到的则是属性和值勾搭的对象.
对于由多个单词构成的属性名, jQuery既可以解释连字符版的css表示法(background-color), 也可以解释驼峰大小写形式的DOM表示法(backgroundColor)
对于带浏览器前缀的属性值(比如-webkit-property-name, -ms-property-name), 用jQuery不需要提前检测, 而可以直接使用标准的属性名. 比如:
.css('propertyName','value'). //如果样式对象中不存在这个属性,jQuery就会依次检测所有带前缀(Webkit, o, Moz, ms)的属性, 然后使用第一个找到的那个属性.
2. 隐藏和显示元素
.hide() .show()
- .hide()方法会把匹配的元素集合的内联style属性设置为display:none. 它可以在把display的值变为none之前, 记住原先的display值
- .show()方法会将匹配的元素集合的display属性,恢复为应用display:none之前的可见属性
可以为hide()和show()方法中指定时长参数, 产生动画效果.比如
.hide('duration')方法会同时减少元素的高度, 宽度和不透明度, 直到三个属性的值都为0. 与此同时会为该元素应用display:none. 而show('duration')方法相反.
此外,还可以指定两种预设的速度参数 'slow' 和 'fast' ,分别在600ms和200ms内完成效果. 如果传入的是其他字符串,jQuery就会在默认的400ms内完成效果.要指定更精确的速度,可以使用毫秒数值. 数值不需要用引号.
一些其他显示/隐藏元素的函数:
//参数: 1. speed(毫秒/'slow'/'normal'/'fast') // 2. callback[执行完的回调参数] //淡入淡出 只是改变不可见性 .fadeIn(); //逐渐增大不透明度, 淡入 .fadeOut(); //逐渐减少不透明度, 淡出 //滑上和滑下 .slideDown() .slideUp() //切换可见性 .slideToggle();
3. 创建自定义动画
.animate():两种形式
- 第一种形式. 接收四个参数
- 一个包含样式属性及值的对象: 与前面讨论的.css()方法中的参数类似
- 可选的时长参数: 既可以是预置的字符串, 也可以是毫秒数值
- 可选的缓动(easing)类型
- 可选的回调函数
.animate({property1: 'value1', property2: 'value2'}, duration, easing, function(){ //... });
- 第二种形式. 接收两个参数
- 一个属性对象
- 一个选项对象.
- 其实就是把第一种形式的第2-4个参数封装在了另一个对象中, 同时又添加了两个选项
.animate({ property1:'value1', property2:'value2' }, { duration:'value', easing: 'value', specialEasing: { property1: 'easing1', property2: 'easing2' }, complete: function(){ //... }, queue: true, step: callback });
当使用 animate() 时,必须使用 Camel 标记法书写所有的属性名,比如,必须使用 paddingLeft 而不是 padding-left,使用 marginRight 而不是 margin-right,等等。
还可以用.animate()为同一组元素应用多重效果, 可以通过连缀这些效果来实现排队
简单概括:
- 一组元素上的效果
- 当在一个.animate()方法中以多个属性的方法应用时, 是同时发生的
- 当以方法连缀的形式应用时, 是按顺序发生的(排队效果)除非queue选项值为false
- 多组元素上的效果
- 默认情况下是同时发生的
- 当在另一个效果方法或者在.queue()方法的回调函数中应用时, 是按顺序发生的(排队效果)
第5章 操作DOM
1. 操作属性
1) .attr() .removeAttr()
//操作非类属性 .attr(); //和.css()方法类似, 可以接收一对属性名/属性值参数 或者是一个包含键值对的对象 .removeAttr(); //还可以使用值回调为每个元素设置不同的属性值 $(document).ready(function() { // Use attr() to add an id, rel, and title. $('div.chapter a[href*="wikipedia"]').attr({ rel: 'external', title: function(){
return 'Learn more about ' + $(this).text() //利用值回调的上下文
+ ' at Wikipedia.';
}, id: function(index, oldValue) { //值回调 return 'wikilink-' + index; } }); });
每次触发值回调, 都会给它传入两个参数. 第一个参数是整数, 表示迭代次数. 第二个参数保存的是修改之前的属性的值, 这里并没有用到.
2) DOM元素属性
HTML属性和DOM属性:
- HTML属性是指页面标记中放在引号中的值
- DOM属性则是指通过JavaScript能够存取的值
在jQuery中, 可以通过.prop()方法取得和设置DOM属性:
//取得"checked"属性的当前值 var currentChecked = $('.my-checkbox').prop('checked'); //设置"checked"属性的值 $('.my-checkbox').prop('checked', false);
3) 表单控件的值
HTML属性和DOM属性差别最大就数表单控件的值. 比如,文本输入框的value属性在DOM中的属性叫defaultValue, DOM中就没有value值. 而选项列表(select)元素, 其选项的值在DOM中通常是通过selectedIndex属性, 或者通过其选项元素的selected属性来取得.
由于存在这些差异, 在取得和设置表达控件值时,最好不要用.attr()方法. 而对于选项列表, 最好也不要用.prop()方法. 建议用jQuery提供的val()方法:
//取得文本输入框的当前值 var inputValue = $('#my-input').val(); //取得选项列表的当前值 var selectValue = $('#my-select').val(); //设置单选列表的值 $('#my-single-select').val('value3'); //设置多选列表的值 $('#my-multi-select').val(['value1','value2']);
2. DOM树操作
-
$() 可以创建新元素
-
插入新元素
- .insertBefore(): 在现有元素外部, 之前添加内容
- .prependTo(): 在现有元素内部, 之前添加内容
- .appendTo(): 在现有元素内部, 之后添加内容
- .insertAfter(): 在现有元素外部, 之后添加内容
// Add "back to top" links. $('<a href="#top">back to top</a>').insertAfter('div.chapter p'); $('<a id="top"></a>').prependTo('body');
-
移动元素
// Create footnotes. $('span.footnote').insertBefore('#footer');
-
包装元素
// Create footnotes. $('span.footnote') .insertBefore('#footer') .wrapAll('<ol id="notes"></ol>') //把所有脚本都包含在一个<ol>中 .wrap('<li></li>') //把每一个脚注分布包装在自己的<li>中
用.each()方法作为显式迭代器, 为提取脚注的位置加标记和编码
-
// Create footnotes.
-
var $notes = $('<ol id="notes"></ol>').insertBefore('#footer');
-
$('span.footnote').each(function(index) {
-
$('<sup>' + (index + 1) + '</sup>').insertBefore(this);
-
$(this).appendTo($notes).wrap('<li></li>');
-
});
-
-
使用反向插入方法
对应的反向方法:
$('<p>Hello</p>').appendTo('#container'); //与下面的代码结果一样 $('#container').append('<p>Hello</p>');
所以上面的代码还可以写作:
// Create footnotes. var $notes = $('<ol id="notes"></ol>').insertBefore('#footer'); $('span.footnote').each(function(index) { $(this) .before('<sup>' + (index + 1) + '</sup>') .appendTo($notes) .wrap('<li></li>'); });
-
复制元素
.clone()
$('div.chapter p:eq(0').clone().insertBefore('div.chapter');
-
元素内容
- .html() //返回匹配元素的html标记, 或替换元素内容(如果有参数)
- .text() //也可以取得匹配元素的内容,或者用新字符替换匹配元素的内容. 但是.text()始终会取得或设置纯文本内容. 所有html标签都将被忽略掉, 而所有html实体也会被转化为对应的字符
-
DOM操作方法的简单总结
//要在html中创建新元素, 使用$()标签 //要在每个匹配的元素中插入新元素: .append() .appendTo() .prepend() .prependTo() //要在每个匹配的元素相邻的位置上插入新元素 .after() .insertAfter() .before() .insertBefore() //要在每个匹配的元素外部插入新元素 .wrap() .wrapAll() .wrapInner() //要用新元素或文本替换每个匹配的元素 .html() .text() .replaceAll() .replaceWith() //要移除每个匹配的元素中的元素 .empty() //要从文档中移除每个匹配的元素及其后代元素, 但不实际删除它们 .remove() .detach()
第6章 通过Ajax发送数据
Ajax实质: 就是一种无需刷新页面即可从服务器(或客户端)上加载数据的手段
$(document).ready(function() { $('#letter-a a').click(function(event) { event.preventDefault(); $.ajaxSetup({ //$.ajaxSetup()函数可以修改调用Ajax方法时每个选项的默认值. //这个函数与$.ajax()接受相同的选项对象参数. 之后所有Ajax请求都使用传递给该函数的选项, 除非明确覆盖 url: 'a.html', type: 'POST', dataType: 'html' }); $.ajax({ type: 'GET', success: function(data) { $('#dictionary').html(data); } }); }); $('#letter-b a').click(function(event) { event.preventDefault(); $.getJSON('b.json', function(data) { //$.getJSON()方法会在取得相应文件后对文件进行处理. 在数据从服务器返回后, 它只是一个简单的JSON格式的文本字符串. //$.getJSON()方法会解析这个字符串, 并将处理得到的JavaScript对象提供给调用代码 //接受两个参数, 第2个参数是当加载完成时调用的函数 //$.getJSON()函数: 没有该方法适用的DOM元素;作为结果的对象只能提供给脚本,而不能插入到页面中. //getJSON()是作为全局jQuery对象(由jQuery库定义的jQuery或$对象)的方法定义的, 而不是个别jQuery对象实例的方法 //可把getJSON()方法看做类方法, 称其为全局函数. 这些全局函数使用的是jQuery命名空间. var html = ''; $.each(data, function(entryIndex, entry) { //$.each()函数不操作jQuery对象, 它以数组或对象作为第一个参数,以回调函数作为第二个参数. //此外还需要将每次循环中数组或对象的当前索引和当前项作为回调函数的两个参数 html += '<div class="entry">'; html += '<h3 class="term">' + entry.term + '</h3>'; html += '<div class="part">' + entry.part + '</div>'; html += '<div class="definition">'; html += entry.definition; if (entry.quote) { html += '<div class="quote">'; $.each(entry.quote, function(lineIndex, line) { html += '<div class="quote-line">' + line + '</div>'; }); if (entry.author) { html += '<div class="quote-author">' + entry.author + '</div>'; } html += '</div>'; } html += '</div>'; html += '</div>'; }); $('#dictionary').html(html); }); }); $('#letter-c a').click(function(event) { event.preventDefault(); $.getScript('c.js'); //$.getScript()也是一个全局函数. 接受一个url参数以查找脚本文件. //以这种方式取得的脚本会在全局环境下执行. 这意味着脚本有权访问在全局环境中定义的函数和变量, 当然也包括jQuery自身 }); $('#letter-d a').click(function(event) { event.preventDefault(); $.get('d.xml', function(data) { //加载xml文档. //通常,$.get()函数只是取得由url指定的文件, 然后将纯文本格式的数据提供给回调函数. 但是在根据服务器提供的mine类型知道相应的是xml的情况下, 提供给回调函数的将是xml dom树 $('#dictionary').empty(); $(data).find('entry').each(function() { var $entry = $(this); var html = '<div class="entry">'; html += '<h3 class="term">' + $entry.attr('term'); html += '</h3>'; html += '<div class="part">' + $entry.attr('part'); html += '</div>'; html += '<div class="definition">'; html += $entry.find('definition').text(); //jQuery内部的选择符引擎, 对查找xml文档元素也有效 var $quote = $entry.find('quote'); if ($quote.length) { html += '<div class="quote">'; $quote.find('line').each(function() { html += '<div class="quote-line">'; html += $(this).text() + '</div>'; }); if ($quote.attr('author')) { html += '<div class="quote-author">'; html += $quote.attr('author') + '</div>'; } html += '</div>'; } html += '</div>'; html += '</div>'; $('#dictionary').append($(html)); }); }); }); $('#letter-e a').click(function(event) { event.preventDefault(); //为了防止单击这些链接时打开新的url. //当默认动作是重新加载页面或重新打开新页面时, 推荐使用preventDefault()而不是return false结束该处理程序. //return false意味着同时调用event.preventDefault()和event.stopPropagation(), 因此想要阻止事件冒泡, 还得调用后者 var requestData = {term: $(this).text()}; $.get('e.php', requestData, function(data) { //向服务器传递数据. 第二个参数是一个用来构建查询关键字符串的键和值的对象. $('#dictionary').html(data); }).fail(function(jqXHR) { $('#dictionary') .html('Sorry, but an error occurred: ' + jqXHR.status) .append(jqXHR.responseText); }); }); $('#letter-f form').submit(function(event) { event.preventDefault(); var formValues = $(this).serialize(); $.get('f.php', formValues, function(data) { $('#dictionary').html(data); }); }); var url = 'http://examples.learningjquery.com/jsonp/g.php'; $('#letter-g a').click(function(event) { event.preventDefault(); $.getJSON(url + '?callback=?', function(data) { //使用JSONP加载远程数据, 实现跨域
//可以通过使用 JSONP 形式的回调函数来加载其他网域的 JSON 数据,如 "url?callback=?"。jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。 注意:此行以后的代码将在这个回调函数执行前执行。
//该函数是简写的Ajax函数. 等价于: $.ajax({url:url, data: data, success: callback, dataType: json});
var html = ''; $.each(data, function(entryIndex, entry) { html += '<div class="entry">'; html += '<h3 class="term">' + entry.term + '</h3>'; html += '<div class="part">' + entry.part + '</div>'; html += '<div class="definition">'; html += entry.definition; if (entry.quote) { html += '<div class="quote">'; $.each(entry.quote, function(lineIndex, line) { html += '<div class="quote-line">' + line + '</div>'; }); if (entry.author) { html += '<div class="quote-author">' + entry.author + '</div>'; } html += '</div>'; } html += '</div>'; html += '</div>'; }); $('#dictionary').html(html); }); }); $('#letter-h a').click(function(event) { event.preventDefault(); $('#dictionary').load('h.html .entry'); }); var $loading = $('<div id="loading">Loading...</div>') .insertBefore('#dictionary'); //jQuery提供的一组观察员函数, 来为各种与Ajax相关的事件注册回调函数. //这些观察员都是全局性的,且只能由$(document)调用 $(document).ajaxStart(function() { //当Ajax请求开始且尚未进行其他传输时出发.ajaxStart()的回调函数 $loading.show(); }).ajaxStop(function() { //最后一次活动请求终止时出发 $loading.hide(); }); //全局观察员函数还有.ajaxError() $('body').on('click', 'h3.term', function() { $(this).siblings('.definition').slideToggle(); //事件委托 }); });
第7章 使用插件
第8章 开发插件
1. 在插件中使用$别名
为了防止jQuery的别名$已经被让渡出去, 可以在插件的作用域内定义这个快捷方式, 使用立即调用的函数表达式(IIFE, Immediately Invoked Function Expression)
(function($){ //code here })(jQuery);
2. 添加新的全局函数
所谓全局函数, 实际上就是jQuery对象的方法, 但从实践的角度上看,它们是位于jQuery命名空间内部的函数. 比如$.ajax(), $.each(), $.map().
给jQuery添加新的全局方法就是只要在第一节中的IIFE内部定义一个方法, 在函数外部就可以调用了. 因为它已经是jQuery的对象方法了. 比如:
/****************************************************************************** Our plugin code comes first in this document. Normally, plugins would appear in separate files named jquery.plugin-name.js, but for our examples it's convenient to place this plugin code in the same JavaScript file as the code that calls it. ******************************************************************************/ /****************************************************************************** $.sum() Return the total of the numeric values in an array/object. ******************************************************************************/ (function($) { $.sum = function(array) { var total = 0; $.each(array, function(index, value) { value = $.trim(value); value = parseFloat(value) || 0; total += value; }); return total; }; $.average = function(array) { if ($.isArray(array)) { return $.sum(array) / array.length; } return ''; }; })(jQuery); /****************************************************************************** End plugin code; begin custom script code. ******************************************************************************/ $(document).ready(function() { var $inventory = $('#inventory tbody'); var quantities = $inventory.find('td:nth-child(2)') .map(function(index, qty) { return $(qty).text(); }).get(); var sum = $.sum(quantities); $('#sum').find('td:nth-child(2)').text(sum); });
此外, 还可以利用$.extend()函数通过另一种语法定义全局函数:
(function($) { $.extend({ sum: function(array) { var total = 0; $.each(array, function(index, value) { value = $.trim(value); value = parseFloat(value) || 0; total += value; }); return total; }, average: function(array) { if ($.isArray(array)) { return $.sum(array) / array.length; } return ''; } }); })(jQuery);
为了避免冲突, 可以把属于一个插件的全局对象都封装到一个对象中. 即使用命名空间隔离函数 .比如:
(function($) { $.mathUtils = { sum: function(array) { var total = 0; $.each(array, function(index, value) { value = $.trim(value); value = parseFloat(value) || 0; total += value; }); return total; }, average: function(array) { if ($.isArray(array)) { return $.mathUtils.sum(array) / array.length; } return ''; } }; })(jQuery);
调用时需要:
var sum = $.mathUtils.sum(quantities); var average = $.mathUtils.average(prices);
3. 添加jQuery对象方法
- 扩展jQuery.fn对象. jQuery.fn是jQuery.prototype的别名
- 因为jQuery的选择符表达式可能会匹配零,一个或多个元素, 所有在设计插件时应该考虑隐式迭代
(function($) { $.fn.swapClass = function(class1, class2) { this.each(function() { var $element = $(this); if ($element.hasClass(class1)) { $element.removeClass(class1).addClass(class2); } else if ($element.hasClass(class2)) { $element.removeClass(class2).addClass(class1); } }); }; })(jQuery);
- 方法连缀. 因此要在所有插件方法中返回一个jQuery对象, 除非相应的方法明显用于取得不同的信息. 返回的jQuery对象通常就是this所引用的对象. 如果使用.each()迭代遍历this, 那么可以只返回迭代的结果. 比如:
(function($) { $.fn.swapClass = function(class1, class2) { return this.each(function() { var $element = $(this); if ($element.hasClass(class1)) { $element.removeClass(class1).addClass(class2); } else if ($element.hasClass(class2)) { $element.removeClass(class2).addClass(class1); } }); }; })(jQuery);
这样, 就可以在插件方法上连缀内置方法了
4. 提供灵活的方法参数
- 合适的时候, 利用回调函数支持灵活地修改插件行为, 从而不必修改插件代码
- 如果插件是为了实现用户界面元素, 或者需要跟踪元素的状态, 使用jQuery UI部件工厂来创建
附录A JavaScript闭包
1. 内部函数
所谓内部函数, 就是定义在另一个函数中的函数. 可以避免污染命名空间.
而JS中的内部函数可以逃脱定义它们的外部函数. 逃脱的方法有多种:
- 可以给内部函数指定给一个全局变量:
var globalVar; function outerFn() { console.log('Outer function'); function innerFn() { console.log('Inner function'); } globalVar = innerFn; } console.log('outerFn():'); outerFn(); console.log('globalVar():'); globalVar();
- 可以通过在父函数中返回值来获得内部函数的引用:
function outerFn() { console.log('Outer function'); function innerFn() { console.log('Inner function'); } return innerFn; } console.log('var fnRef = outerFn():'); var fnRef = outerFn(); //Outer function console.log('fnRef():'); fnRef();//Inner function
当内部函数在定义它的作用域的外部被引出时, 就创建了该内部函数的一个闭包.
这种情况下, 我们称既不是内部函数局部变量, 也不是其参数的变量为自由变量, 称外部函数的调用环境为封闭闭包的环境. 从本质上将, 如果内部函数引用了位于外部函数中的变量, 相当于授权该变量能够被延迟使用. 因此, 当外部函数引用完成后, 这些变量的内存不会被释放, 因为闭包仍然需要使用它们.
2. 在jQuery中创建闭包
依然要注意以下这个问题:
$(document).ready(function($) { // Stuff to do as soon as the DOM is ready; for(var i=0; i<5; i++){ $('<div>Print ' + i + '</div>') .click(function(){ console.log(i); }).insertBefore('body'); } });
这里单击其中一项并不会看到相应的编号输出, 而是都会显示数值5. 即即使在绑定程序时i的值每次都不一样, 每个click处理程序最终引用的i都相同. 都等于单击事件实际发生时i的最终值(5).
要解决这个问题, 方法有:
-
使用jQuery的$.each()函数来代替for循环:
$(document).ready(function() { // Stuff to do as soon as the DOM is ready; $.each([0,1,2,3,4], function(index, value) { $('<div>Print ' + value + '</div>') .click(function(){ console.log(value); }).insertBefore('body'); }); });
这是因为函数的参数类似于在函数中定义的变量, 所以每次循环的value值实际上都是不同的变量. 结果, 每个click处理程序都指向一个不同的value变量, 因而每次单击输出的值会与元素的标签文本匹配.
-
在for循环内部, 可以定义并执行一个新函数: 即使用立即调用的函数表达式(IIFE)
$(document).ready(function() { for(var i=0; i<5; i++){ (function(value){ $('<div>Print '+ value + '</div>') .click(function(){ console.log(value); }).insertBefore('body'); })(i); } });
-
使用jQuery的事件系统, 利用.on()方法.
该方法接受一个对象参数, 该参数以event.data的形式传入事件处理函数中
$(document).ready(function() { for(var i=0; i<5; i++){ $('<div>Print '+ i + '</div>') .on('click', {value: i}, function(event){ console.log(event.data.value); }).insertBefore('body'); } });
因为event是函数的参数, 每次调用处理程序时它都是一个独立的实例, 而不是在所有调用中共享的一个值.
3. 应对内存泄露的风险
1) 避免意外的引用循环
闭包可能会导致在不经意间创建引用u型拟合. 比如:
function outerFn() { var outerVar = {}; function innerFn() { console.log(outerVar); } outerVar.fn = innerFn; return innerFn; };
这里innerFn()创建了一个引用outerVar的闭包, 而outerVar又引用了innerFn()
而更隐蔽的情形是:
function outerFn() { var outerVar = {}; function innerFn() { console.log('hello'); } outerVar.fn = innerFn; return innerFn; };
这里虽然innerFn()没有引用outerVar. 但是仍然没有断开循环. 即使innerFn()不再引用outerVar, outerVar也仍然位于innerFn()的封闭环境中. 由于闭包的原因, 位于outerFn()中的所有变量都隐含地被innerFn()所引用. 因此, 闭包会使意外地创建这些引用循环变得容易.
2) 控制DOM与JavaScript的循环
以上这种情况通常容易处理, 因为js能够检测到这些情况并在它们孤立时将其清除.
旧版本IE中存在的一种难以处理的引用循环问题. 当一个循环中同时包含DOM元素和常规JS元素时, IE无法释放任何一个对象----因为这两类对象是由不同的内存管理程序负责管理的. 即除非关闭浏览器, 否则这种循环在IE中永远得不到释放. 比如:
$(document).ready(function() { var button = document.getElementById('button-1'); button.onclick = function() { console.log('hello'); return false; }; });
当指定单击事件处理程序时, 就创建了一个在其封闭的环境中包含button变量的闭包. 而且, 现在的button也包含一个指向闭包(onclick属性自身)的引用. 这样, 就导致了在IE中即使离开当前页面也不会释放这个循环.
为了释放内存, 就需要断开循环引用, 例如在关闭窗口关删除onclick属性. 另外, 可以向如下重写代码来避免:
function hello() { console.log('hello'); return false; } $(document).ready(function() { var button = document.getElementById('button-1'); button.onclick = hello; });
这样,因为hello函数不再包含button, 引用就成了单向的(从button到hello), 不存在的循环, 就不会造成内存泄露了.
3) 用jQuery化解引用循环
$(document).ready(function() { var $button = $('#button-1'); $button.click(function(event) { event.preventDefault(); console.log('hello'); }); });
即使此时仍然会创建一个闭包, 并且也会导致同前面一样的循环, 但这里的代码却不会使IE发生内存泄露. 由于jQuery考虑到了内存泄露的潜在危害, 所以它会手动释放自己指定的所有事件处理程序.
另一种避免泄露的工具-----使用.data()方法可以像使用扩展属性一样, 将信息附加到DOM元素. 由于这里的数据并非直接保存在扩展属性中, 因此永远也不会构成引用循环.