Copyright © 2010
Licensed by Rebecca Murphey under the Creative Commons Attribution-Share Alike 3.0 United States license. You are free to copy, distribute, transmit, and remix this work, provided you attribute the work to Rebecca Murphey as the original author and reference the GitHub repository for the work. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license. Any of the above conditions can be waived if you get permission from the copyright holder. For any reuse or distribution, you must make clear to others the license terms of this work. The best way to do this is with a link to the license.
Table of Contents
List of Examples
true
的值false
的值for
循环while
循环while
循环,在条件判断部分对计数器i进行自加do-while
循环i
的值?i
的值$.fn.end
恢复你原来的选择器$.fn.html
方法作为一个 setter 使用$.fn.data
存储元素之间的关系$.fn.bind
方法绑定事件$.fn.bind
方法将数据绑定到事件$.fn.one
方法转换处理函数$.fn.delegate
委托事件$.fn.live
委托事件jQuery.fx.speeds
自定义动画速度$.fn.animate
自定义效果$.fn.load
to populate an element$.fn.load
to populate an element based on a
selectorjQuery正迅速地变成前端开发人员必需技能。 这本书的目的是提供jQuery JavaScript库的概述;当你完成这本书阅读的时候, 你应该能够用jQuery完成基本的任务并且有能继续学习的坚实基础。 这本书是作为课堂教学教材设计的,但你会发现它对自学也很有用。
这是一个需要动手的课程。我们将会花费一点时间说明一个概念, 然后你将会有机会去做一个与这个概念有关的练习。 可能有些练习会让你觉得烦琐;其它的可能是十足令人气馁的。 这些练习都是没有评分的; 练习的目的只是让你在工作中能轻松地解决使用jQuery常会出现的问题。 练习的示例解决方案都被包含在示例代码中。
这本书用到的代码托管在 Github的库里。 你也可以下载代码的.zip或者.tar文件,解压后在你的服务器上使用。 如果你愿意使用git,欢迎你复制或fork这个库。
在大部分的课程中你会需要以下的工具:
Firefox浏览器
Firefox附加组件Firebug
纯文本编辑器
Ajax部分:本地服务器(例如MAMP、WAMP), 或者可以访问远程服务器的FTP、SSH客户端
JavaScript可以内嵌在页面里,也可以引用外部使用script标签的文件。 引用JavaScript的先后顺序很重要:脚本必须在依赖其的脚本前被引用。
出于页面性能考虑,应在离HTML结尾尽可能近的地方引用JavaScript。 数量较多的JavaScript文件应在部署的时候合并起来。
调试工具对JavaScript开发来说是很重要的。 Firefox提供了一款叫Firebug的调试器,这个调试器可以通过附件组件获得; Safari和Chrome则内嵌了调试控制台。
各种控制台的功用:
用来编写JavaScript的单行或多行编辑器
用来查看生成的页面源代码的监视器
用来检查网络请求情况的网络或资源视图
当你在编写JavaScript代码的时候,可以通过如下的方法在控制台输出消息:
输出普通日志消息:console.log()
输出可浏览的对象的日志:console.dir()
输出警告日志:console.warn()
输出错误日志:console.error()
还有其他的控制台方法,但是它们可能会因为浏览器而有所不同。 控制台还提供了设置断点、查看代码中的表达式等方法以供调试使用。
这本书大多数的章节都以一个或多个练习来作出结论。 其中的一些练习,你可以直接在Firebug里完成; 其他的你需要像独立的练习指导的那样,在jQuery脚本标签后引用其他的脚本。
在一些情况下,你需要按顺序查阅jQuery的文档来完成一个练习, 毕竟这本书没有覆盖所有的相关信息。 这是故意的,因为jQuery有很庞大的文档库, 学习如何在文档中找答案是一个很重要的学习过程。
解决问题的几点建议:
首先,你需要确定你完全理解了你要解决的问题。
接下来,确定你要从哪个元素入手解决问题,然后决定如何获得这些元素。 使用Firebug核实你确实获得了你想要的元素
最后,确定你要如何处理这些元素以解决问题。 在写代码前先写注释解释你将如何处理将会很有帮助。
不要害怕犯错误!不要在第一次的时候就试图让你的代码完美!
犯错误然后尝试不同解决方法是学习这个库的一个部分,通过尝试,你会变成一个更好的开发者。
练习的示例解决方案可以从示例代码的/solutions
目录获得。
可以被jQuery对象调用的方法使用$.fn.methodName
描述。
jQuery命名空间中存在的但是不能被jQuery对象调用的方法使用$.methodName
描述。
如果你不能理解这些,不用担心,随着你对本书阅读的深入,这些概念会越来越清晰。
评论以这种样式显示。
关于标题的批注以这种样式显示。
有很多文章和博客涉及jQuery的一些方面。 其中有一些是了不起的;一些是完全错误的。 当你在阅读这些关于jQuery的文章时,要确定它说的是你正在使用的版本, 不要马上复制粘贴代码——花点时间去理解文章里的代码。
这里有一些很优秀的资源可供学习jQuery的过程中使用。 这些jQuery资源最重要的地方是jQuery的源代码:它以代码的形式包含了库里的所有文档。 这不是一个黑盒子——你对库的理解将随着你重复的阅读而呈现指数级的深入—— 我非常建议将这些资源添加到你浏览器的书签中以便经常查阅。
jQuery 构建在 JavaScript 之上,是一个丰富和极具表现力的语言。这一章节将覆盖 JavaScript 的基本概念,除一些常见陷阱外,大家在此之前不需要使用过 JavaScript。 除个别的地方外也不需要有程序设计经验,有过其它编程语言的使用经验也会对 javaScript 的学习有所帮助。
如果你有兴趣了解更多JavaScript语言,我强力推荐你阅读由Douglas Crockford编写的JavaScript: The Good Parts。
理解语句,变量名称,空格,制表符,以及其他的一些基本javascript语法
通过基本运算符可以方便的操作数据的值
Example 2.5. 连接字符串
var foo = 'hello'; var bar = 'world'; console.log(foo + ' ' + bar); // 'hello world'
Example 2.7. 自加和自减
var i = 1; var j = ++i; // 自加运算符在i之前: j 等于 2; i 等于 2 var k = i++; // 自加运算符在i之后: k 等于 2; i 等于 3
在javascript中,对数字和字符串的操作,偶尔会得到意料之外的结果
上例中,当Number对象的构造函数作为一个函数被调用的时候,它会把传入的参数自动转换成数字。除了这种方法,你还可以使用一元运算符+进行强制类型转换,如下例所示。
逻辑运算符使用and和or对操作数进行逻辑判断
Example 2.11. 逻辑与和逻辑或操作符
var foo = 1; var bar = 0; var baz = 2; foo || bar; // 返回1,也就是返回true,判断结果为真。 bar || foo; // 返回1。 foo && bar; // 返回0,也就是返回false,判断结果为假。 foo && baz; // 返回baz的,返回值为2,判断结果为真。 baz && foo; // 返回1,判断结果为真。
也许无法从例子中清晰的反应出来,其中或运算符( ||
)
返回第一个值为真的操作数, 如果两个操作数的值都为假,则返回最后一个操作数。
类似的,与运算符( &&
) 返回第一个值为假的运算符,如果两个操作数的值都为真,则返回最后一个操作数
跳到the section called “真与假” 查看更多关于什么时候判断运算结果为true
和什么时候判断运算结果为
false
的资料。
你有的时候会看到开发者使用以下语句代替if进行流程控制
// 当foo的值为真的时候执行doSomething来处理foo foo && doSomething(foo); // 如果baz为真,则把baz的值赋给bar // 否则bar的值为函数craetBar的返回值 var bar = baz || createBar();
这种风格的代码非常的简洁和优雅,但是同时,特别对于初学者而言,往往晦涩难懂。把它放在这里是希望当你在阅读到这类代码的时候可以读懂。但是如果你还不能够理解它的本质,且没有熟练掌握的话,我们不建议你使用此类代码
比较运算符用来判断两个操作数是否相等
Example 2.12. 比较运算符
var foo = 1;
var bar = 0;
var baz = '1';
var bim = 2;
foo == bar; // 返回false,返回假。
foo != bar; // 返回true,返回真。
foo == baz; 返回真,(注意!)
foo === baz; // 返回false
foo !== baz; // 返回true
foo === parseInt(baz); // 返回true
foo > bim; // 返回false
bim > baz; // 返回true
foo <= baz; // 返回true
有时,你需要当符合某种特定条件的时候再运行你的代码。你可以通过流程控制语句if-else
,来让代码在符合某种条件的时候运行。
Example 2.13. 流程控制
var foo = true; var bar = false; if (bar) { // 这里的代码将不会运行。 console.log('hello!'); } if (bar) { // 这里的代码不会运行。 } else { if (foo) { // 这里的代码会运行 } else { // 如果foo和bar都为false,则这里的代码会被运行。 } }
虽然当if
语句后面只跟有一行代码时,该代码不加括号也是可以运行的。但是为了代码可读性,代码前后通常也加上括号
请注意,不要在if
/esle
代码段中多次定义同一个函数,这会引起意想不到的结果
为了熟练的使用流程控制语句,我们必须要知道在变量值中哪些类型的值会被判定为真,哪些类型的值会被判定为假,有时看起来像是假的值却被判定为真
Example 2.14. 被判断义为 true
的值
'0'; //字符串0。 'any string';//任意字符串。 []; // 一个空数组。 {}; // 一个空对象。 1; // 不为0的数。
Example 2.15. 被判定为false
的值
0; ''; // 一个空串。 NaN; // JavaScript中的 "not-a-number"(NaN是一个不确定的数)。 null; undefined; // 注意 -- undefined 可以被重新定义!
有时你想要根据某些条件给一个变量赋值,你可以使用 if
/else
语句,
但是使用三元运算符会更有效率 [Definition:
三元运算符 通过判定条件,如果条件为真返回第一个值
如果条件为假,返回第二个值]
三元运算符不一定必须要把返回值赋值给变量。
比起写一连串的if/else。我们有时可以使用switch语句来代替。 [Definition: Switch 分支语句 分支语句根据一个 变量或者表达式的值,来运行不同的代码段。]
Example 2.17. 一个switch分支语句
switch (foo) { case 'bar': alert('the value was bar -- yay!'); break; case 'baz': alert('boo baz :('); break; default: alert('everything else is just ok'); break; }
Switch语句在JavaScript中并不是经常被人们用到,通常人们可以通过创建 一个对象来更好的完成这项工作,下面就是一个例子。
var stuffToDo = { 'bar' : function() { alert('the value was bar -- yay!'); }, 'baz' : function() { alert('boo baz :('); }, 'default' : function() { alert('everything else is just ok'); } }; if (stuffToDo[foo]) { stuffToDo[foo](); } else { stuffToDo['default'](); }
我们在以后的章节来了解对象。
循环语句可以让一段代码执行一定的次数。
Example 2.18. 循环语句
// logs 'try 0', 'try 1', ..., 'try 4' for (var i=0; i<5; i++) { console.log('try ' + i); }
在 Example 2.18, “循环语句”,即使我们在变量名 i
前使用关键字var,这也并不意味着 i
变量的作用域在
循环体内。我们将在后面的章节讨论变量的作用域。
for
循环由以下几个部分构成
for ([initialisation初始化]; [conditional条件判断]; [iteration迭代]) [loopBody循环体]
初始化部分在循环开始前执行一次。可以在此声明循 欢中需要用到的变量
条件判断部分在每次循环前执行,它的返回值决定了 循环是否继续。如果返回值为false,那么循环将被终止
迭代部分该部分在每次循环结束时执行,你可以借此改变重要变 量的状态和值。通常在这里进行自加和自减运算,让循环更接近终止。
循环体 每次循环时执行,你可以在( {...}
)中放入你想运行的代码。
这是一个典型的 for
循环:
Example 2.19. 典型的for
循环
for (var i = 0, limit = 100; i < limit; i++) { // 代码将要执行100次。 console.log('Currently at ' + i); // 最后输出的i值为99。 }
while
循环和if
语句类似,除了它的函数体
会一直循环执行,直到条件部分返回false。
while ([条件判断]) [循环体]
这里是一个典型的 while
循环:
Example 2.20. 典型的 while
循环
var i = 0; while (i < 100) { // 代码会被执行100次。 console.log('Currently at ' + i); i++; // i的自加。 }
你会看到我们在循环体内进行计数器i的自加运算。事实上我们也可以在条件判断部分进行自加运算,比如:
Example 2.21. while
循环,在条件判断部分对计数器i进行自加
var i = -1; while (++i < 100) { // 这里的代码会被执行100次 console.log('Currently at ' + i); }
注意我们是从-1
开始并且把自加运算符放在变量前(++i
).
该循环和while循环类似,循环体在条件判断之前执行。
do [循环体] while ([条件判断])
这是一个 do-while
循环:
Example 2.22. 一个 do-while
循环
do { // 即使条件判断返回值为false,循环体还是被执行了一次。 alert('Hi there!'); } while (false);
这些循环类型很少用,仅仅在至少执行一次的情况下才需要这种循环。无论如何,意识到这一点就很好。
JavaScript 有一些保留的 “关键字,” 或者这些单词在语言中有其它含义。 除非你以那样的含义使用它们,否则你应该避免在你的代码中使用这些关键字。
break
case
catch
continue
default
delete
do
else
finally
for
function
if
in
instanceof
new
return
switch
this
throw
try
typeof
var
void
while
with
abstract
boolean
byte
char
class
const
debugger
double
enum
export
extends
final
float
goto
implements
import
int
interface
long
native
package
private
protected
public
short
static
super
synchronized
throws
transient
volatile
数组是一列没有索引的数值。它们是存储一组相同类型(如字符串)数组项的便捷方法,尽管实际上,一个数组可以包含多种类型数组项,甚至包含其它数组。
Example 2.26. 通过索引访问数组项
var myArray = [ 'hello', 'world', 'foo', 'bar' ]; console.log(myArray[3]); // logs 'bar'
当数组值可能以 Example 2.28, “改变数组项值” 的方式改变时,它们通常不被考虑。
Example 2.30. 关于数组
var myArray = [ 'h', 'e', 'l', 'l', 'o' ]; var myString = myArray.join(''); // 'hello' var mySplit = myString.split(''); // [ 'h', 'e', 'l', 'l', 'o' ]
对象包含一个或多个key-value对。其中 key 可以是任何字符。value 部分可以是任何数值类型:a number, a string, an array, a function, 甚至可以是另外一个 object。
[Definition: 当这些数值中有一个函数时,它们调用对象的一个 method 方法。] 否则,它们调用属性。
恰好,JavaScript 中所有东西都是一个对象 - 数组、函数、成员、甚至字符串 — 它们都拥有属性和方法。
Example 2.31. 创建一个对象常量
var myObject = { sayHello : function() { console.log('hello'); }, myName : 'Rebecca' }; myObject.sayHello(); // logs 'hello' console.log(myObject.myName); // logs 'Rebecca'
当创建一个对象常量时,你应该注意每一对 key-value 中的 key 都能被写成任何有效的 javaScript 标识符,一个字符串(带引号) 或是一个数值:
var myObject = { validIdentifier: 123, 'some string': 456, 99999: 789 };
对象常量在代码组织上非常有用;更多信息,请阅读 Rebecca Murphey 写得 Using Objects to Organize Your Code。
函数是包含需要反复执行的代码块。函数可以没有参数或包含多个参数,能返回任意一个值。
函数的创建有多种方法:
在设置一个函数名时我更喜欢命名函数表达式,这有相当的深度和技术因素。 你可能在其它的 JavaScript 代码中看到过这两种方法的使用。
Example 2.34. 一个简单函数
var greet = function(person, greeting) { var text = greeting + ', ' + person; console.log(text); }; greet('Rebecca', 'Hello');
Example 2.35. 函数返回一个值
var greet = function(person, greeting) { var text = greeting + ', ' + person; return text; }; console.log(greet('Rebecca','hello'));
Example 2.36. 函数返回另一个函数
var greet = function(person, greeting) { var text = greeting + ', ' + person; return function() { console.log(text); }; }; var greeting = greet('Rebecca', 'Hello'); greeting();
JavaScript中一个常见的模式是自执行匿名函数。这个模式创建一个函数表述式然后立即执行函数。这个模式为避免你在代码中乱用全局命名空间将非常有用 -- 没有在函数内声明的变量在函数外也是可见的
Example 2.37. 一个自执行的匿名函数
(function(){ var foo = 'Hello world'; })(); console.log(foo); // undefined!
在 JavaScript 中, 函数是"一等公民" -- 它们能赋予变量或作为参数传递给其它函数。函数作为参数传递在 jQuery 中是一个非常普遍的习惯。
Example 2.38. 以参数的方式传递一个匿名函数
var myFn = function(fn) { var result = fn(); console.log(result); }; myFn(function() { return 'hello world'; }); // logs 'hello world'
Example 2.39. 以参数的方式传递一个命名函数
var myFn = function(fn) { var result = fn(); console.log(result); }; var myOtherFn = function() { return 'hello world'; }; myFn(myOtherFn); // logs 'hello world'
JavaScript 提供了一个方法来测试变量类型。但是,其结果可能是令人困惑的 -- 比如,一个数组类型是 "object"。
当准备检测特定值的类型时,通常惯例是使用 typeof
运算符。
Example 2.40. 测试各种变量的类型
var myFunction = function() { console.log('hello'); }; var myObject = { foo : 'bar' }; var myArray = [ 'a', 'b', 'c' ]; var myString = 'hello'; var myNumber = 3; typeof myFunction; // 返回 'function' typeof myObject; // 返回 'object' typeof myArray; // returns 'object' -- careful! typeof myString; // 返回 'string'; typeof myNumber; // 返回 'number' typeof null; // returns 'object' -- careful! if (myArray.push && myArray.slice && myArray.join) { // 可能是个数组 // (这叫动态类型"duck typing") } if (Object.prototype.toString.call(myArray) === '[object Array]') { // 定义一个数组! // 这是一个被广泛验证过的可靠方法 // to 判断一个特殊值是不是一个数组。 }
jQuery 提供了实用方法来帮助你检测一个任意值的类型。这些内容将在后面涉及。
"Scope" 引用的变量在指定时间内的一段代码中的可用性refers to the variables that are available to a piece of code at a given time. 一个不标准的作用域将导致令人沮丧的调试体验。
当使用 var
关键字描述函数中的一个变量时,它作为那个函数中的代码仅仅是个变量 -- 而函数外的代码不能访问这个变量。在其它方面,定义为 inside 的函数 will 能访问到所描述的变量。
此外,在函数里没有使用 var
关键字描述的变量对函数来讲不是局部的 -- JavaScript 将贯穿所有作用域直到 window 作用域以发现之前定义的变量位置。如果变量之前没有定义,它将创建一个全局变量,这会是一个意想不到的结果。
Example 2.41. 函数访问同一作用域内定义的变量
var foo = 'hello'; var sayHello = function() { console.log(foo); }; sayHello(); // logs 'hello' console.log(foo); // also logs 'hello'
Example 2.42. 代码作用域外定义的变量不能访问该变量Code outside the scope in which a variable was defined does not have access to the variable
var sayHello = function() { var foo = 'hello'; console.log(foo); }; sayHello(); // logs 'hello' console.log(foo); // doesn't log anything
Example 2.43. 拥有相同名字的变量可以以不同的值存在不同的作用域中
var foo = 'world'; var sayHello = function() { var foo = 'hello'; console.log(foo); }; sayHello(); // logs 'hello' console.log(foo); // logs 'world'
Example 2.44. 函数定义后可看见变量值的改变
var myFunction = function() { var foo = 'hello'; var myFn = function() { console.log(foo); }; foo = 'world'; return myFn; }; var f = myFunction(); f(); // logs 'world' -- uh oh
Example 2.45. Scope insanity
// 一个自执行的匿名函数 (function() { var baz = 1; var bim = function() { alert(baz); }; bar = function() { alert(baz); }; })(); console.log(baz); // baz 在函数外没有定义 bar(); // bar 在匿名函数外被定义 // 因为它没有描述为var;而且, // 因为它是作为 baz 在同一作用域中被定义的, // 它能访问 baz 即使其它代码 // 在函数之外 bim(); // bim 没有在匿名函数外定义, // 于是其结果将是一个错误
闭包是作用域概念的一个扩展 — 函数能访问函数创建时作用域的变量functions have access to variables that were available in the scope where the function was created. 如果这很困惑,不要担心:通过例子你能更好的理解闭包。
在 Example 2.44, “函数定义后可看见变量值的改变” 我们了解到函数是如何访问到改变的变量值we saw how functions have access to changing variable values. 这种行为同样存在于循环的函数定义中 -- The same sort of behavior exists with functions defined within loops -- 函数观察到变量在函数定义后的改变,产生5次点击警告。the function "sees" the change in the variable's value even after the function is defined, resulting in all clicks alerting 5.
Example 2.46. 如何锁定i
的值?
/* this won't behave as we want it to; */ /* every click will alert 5 */ for (var i=0; i<5; i++) { $('<p>click me</p>').appendTo('body').click(function() { alert(i); }); }
Example 2.47. 以一个闭包的方式锁定i
的值
/* fix: “close” the value of i inside createFunction, so it won't change */ var createFunction = function(i) { return function() { alert(i); }; }; for (var i=0; i<5; i++) { $('<p>click me</p>').appendTo('body').click(createFunction(i)); }
你不能安全的操作此文档,除非文档处于 “ready.” 状态
jQuery 会为你检测准备状态是否就绪; 代码包含的
$(document).ready()
只有在页面被 JavaScript 代码执行时才运行。
这是 $(document).ready()
的简写,那样你就可以不时查看它; 然而, 如果你使用jQuery并不是那样娴熟,我建议你最好别用它。
你也能传递一个命名函数到
$(document).ready()
用来替换匿名函数的传递。
这是 jQuery 最基础的概念,意思是 “选择一些元素然后通过它们做一些事” 除一些非标准的选择器外,jQuery 支持大部分的 CSS3 选择器。若要了解完整的选择器参考, 请访问 http://api.jquery.com/category/selectors/.
以下是一些通用选择技术的例子
Example 3.8. 伪选择器
$('a.external:first'); $('tr:odd'); $('#myForm :input'); // 在一个form中选择所有的输入项 $('div:visible'); $('div:gt(2)'); // 除去1,3之外的所有项 $('div:animated'); // 所有当前项动态区分 all currently animated divs
当你使用 :visible
和 :hidden
伪选择器时, jQuery 会测试元素的真实可见性, 而不是它的CSS的可见性或显示 — 就是说, 如果physical height and width on the page 都大于零,它就可以看见. 然而,这个测试不能与
<tr>
元素一起工作; 在这种情况下, jQuery 检测
CSS display
属性, 如果
它的 display
属性被设置成 none
那就认为这个元素将隐藏。
即使 CSS 将影响它们的视觉渲染,元素也不会被添加到被隐藏的DOM中。 (请看这一章的后面操作小节有关如何创建和增加元素到DOM中的介绍。)
作为参考, 这里的jQuery代码被用于检测此元素是显示还是隐藏,为了清楚也添加了注释:
jQuery.expr.filters.hidden = function( elem ) { var width = elem.offsetWidth, height = elem.offsetHeight, skip = elem.nodeName.toLowerCase() === "tr"; // 元素的高度为0,宽度为0, // 并且它不是 <tr>? return width === 0 && height === 0 && !skip ? // 所以它必须隐藏 true : // 但是如果它拥有高度和宽度值 // 并且它不是 <tr> width > 0 && height > 0 && !skip ? // 所以它必须显示 false : // 如果我们到了这,元素拥有宽度 // 和高度, 但它也是一个 <tr>, // 所以检查显示属性以 // 确定是否隐藏 jQuery.curCSS(elem, "display") === "none"; }; jQuery.expr.filters.visible = function( elem ) { return !jQuery.expr.filters.hidden( elem ); };
一旦你已经指定一个选择器,你将希望知道它是否正常工作。你可能倾向于这样做:
if ($('div.foo')) { ... }
这不会工作的。当你使用 $()
指定一个选择器,一个对象将返回,对象也将计算为 true
。 即使你的选择器没有包含任何元素,包含 if
语句的代码仍将执行。
相反,你需要测试选择器的长度属性,并告诉你包含了多少元素。如果回答是 0, 当以布尔值使用时候起长度属性将被赋予 false // 当长度属性作为布尔值时它的计算值将为 false。
每次你指定了一个选择器,一段代码运行,jQuery 都不会为你做任何的 caching 工作。如果你已经指定了选择器而你又需要重新指定它时,它将在变量中保存选择器,而不是反复的指定它。
在 Example 3.10, “在变量中保存选择器”, 变量名以一个美元符号开始。不像在其它的语言中,在 JavaScript 中没有特定的指定美元符号 -- 而是使用其它字符 。我们在这里使用它是为了表明变量包含一个 jQuery 对象。这是惯例 -- 一种 匈牙利命名法 -- 这仅仅是惯例,而不是强制性的。
一旦你保存了选择器,你就能在保存的变量中调用 jQuery 方法,就像在原来的选择器中调用一样。
只有当你指定一个选择器后,选择器才会从页面取回元素。如果你添加元素到页面延迟了,你必须重新选择或将它们添加到存储在变量中的选择器中。当 DOM 改变时被保存的选择器不会自动更新。
有时你有一个选择器包含比此后的还要多;在这种情况下,你需要改善你的选择器。 jQuery 提供了多种方法用来校正你之后的精确 offers several methods for zeroing in on exactly what you're after.
Example 3.11. 改善选择器
$('div.foo').has('p'); // 包含lt;p> 的元素 $('h1').not('.bar'); // h1 元素被包含 bar 这个类 $('ul li').filter('.current'); // 当前类的所有无序项 $('ul li').first(); // 第一项 $('ul li').eq(5); // 第六项
jQuery 提供了多个伪选择器帮助你发现表单中的元素;这些尤其有帮助,因为它能在使用标准CSS选择器的基于状态或类型的form元素中艰难区分these are especially helpful because it can be difficult to distinguish between form elements based on their state or type using standard CSS selectors.
选择 <button>
元素 和 元素type="button"
类型
选择输入 type="checkbox"
类型
选择选中输入
选择禁用表单元素
选择启用表单元素
选择 type="file"
输入类型
选择 type="image"
输入类型
选择 <input>
,
<textarea>
, 和 <select>
元素
选择 type="password"
密码类型
选择 type="radio"
输入类型
选择 type="reset"
输入类型
选择被选中的选项
选择 type="submit"
输入类型
选择 type="text"
输入类型
一旦你指定一个选择器, 你就能调用它的方法。 这些方法通常带来两种不同的口味: getters 和 setters. Getters 返回第一个选择元素的属性;setters 设置所有选择元素为同一属性。
如果你调用一个选择器方法,其方法返回一个 jQuery 对象,你能继续调用这个对象的 jQuery 方法而不用处理分号。
如果你要写个chain包含多个步骤,如果你以多行的方式处理 chain,那么你(和之后进来的人)可能会发现你的代码更具可读性。
如果你在链中修改你的选择器,jQuery 提供了 $.fn.end
方法帮你返回到你原来的选择器。
Example 3.15. 使用
$.fn.end
恢复你原来的选择器
$('#content') .find('h3') .eq(2) .html('new text for the third h3!') .end() // 恢复选择器到 #content 中的所有 h3 部分 restores the selection to all h3's in #content .eq(0) .html('new text for the first h3!');
链式是非常强大的,自从它随着 jQuery 流行起来后众多 JavaScript 库都加入了此功能。 然而,它必须被小心使用。大量的链式处理会给代码的修改和调试带来更大的困难。这里没有链要保持多久所必须遵守的规则 -- 仅仅知道它是比较容易被搬运的。
jQuery “overloads”(重载) 它的方法, 此方法被用于设置值或取得一个值 so the method used to set a value generally has the same name as the method used to get a value. 当方法被用于设置值时, 它将调用一次 setter. 当方法被用于取得(或读取)值时, 它将调用一次 getter. Setters 作用于选择器里的所有元素; getters 将取得选择器匹配的第一个元素。
Setters 返回一个 jQuery 对象, 允许你在选择器中继续调用 jQuery 方法; getters 返回它们想要的 ,这意味着你不能在使用 getter 的返回值中继续调用 jQuery 方法。
jQuery 包含便捷的方法取得和设置元素的 CSS 属性。
CSS 属性通常包含一个连字符而在 JavaScript 中是
camel cased 形式。比如, CSS
属性 font-size
在 JavaScript 表示为 fontSize
。
Example 3.19. Setting CSS properties
$('h1').css('fontSize', '100px'); // 设置一个属性 $('h1').css({ 'fontSize' : '100px', 'color' : 'red' }); // 设置多个属性
参数样式的注释我们将放在第二行 -- 一个对象将包含多个属性。这是传递多个参数到函数的通用方法,更多 jQuery setter 方法接受对象一次性设置多个值。
作为一个 getter, $.fn.css
方法是有价值的;
然而, 通常要避免在产品代码中使用 setter, 因为你不想要在你的 JavaScript 中包含展示信息。相反,为类写CSS规则用以描述各种虚拟状态,简单的改变你需要影响的元素的类。
Example 3.20. 使用类
var $h1 = $('h1'); $h1.addClass('big'); $h1.removeClass('big'); $h1.toggleClass('big'); if ($h1.hasClass('big')) { ... }
类用于存储元素的状态信息时也非常有用,比如表明选择的元素。
jQuery 提供了多个方法来获得和修改元素的大小和位置信息。
Example 3.21, “基本尺寸方法” 的代码简单描述了 jQuery 的尺寸功能; 若要获得有关jQuery尺寸方法完整的信息,请访问http://api.jquery.com/category/dimensions/.
Example 3.21. 基本尺寸方法
$('h1').width('50px'); // 设置所有H1元素的宽度 $('h1').width(); // 获得第一个H1元素的宽度 $('h1').height('50px'); // 设置所有H1元素的高度 $('h1').height(); // 获得第一个H1元素的高度 $('h1').position(); // 返回对象的坐标位置 // 相对于第一个H1的信息 // 它的父类偏移位置
一个元素属性能包含有应用的有用信息,所以取得和设置它们是非常重要的。
$.fn.attr
方法充当getter和setter。在 $.fn.css
方法中, $.fn.attr
作为一个 setter 即能接受一键一值,也能作为一个对象包含一个或多个键/值对。
Example 3.22. 设置属性
$('a').attr('href', 'allMyHrefsAreTheSameNow.html'); $('a').attr({ 'title' : 'all titles are the same too!', 'href' : 'somethingNew.html' });
这次, 我们分解对象为多行。记住,在 JavaScript 中,空白是无关紧要的,这样你就可以自由的使用它让你的代码更具可读性!最后成品时你能使用压缩工具将不需要的空白给去除掉。
一旦你拥有jQuery选择器,你就能使用它作为起始点去发现其它的元素。
jQuery遍历方法完整的文档,请访问http://api.jquery.com/category/traversing/.
在遍历很长的文档时要小心 -- 完整的遍历有必要保留你的文档结构,除非你从服务端到客户端创建了完整应用,否则一些事将很难保证。一个或两个步骤的遍历是好的,但是当你从一个容器到另一个容器时需要避免遍历的发生。
Example 3.24. 使用遍历方法围绕DOM移动
$('h1').next('p'); $('div:visible').parent(); $('input[name=first_name]').closest('form'); $('#myList').children(); $('li.selected').siblings();
你也能通过一个使用$.fn.each
的选择器对所有元素进行迭代,为每个元素都运行一次函数。函数将接收当前元素的索引和带有参数的DOM元素。在函数内部,默认情况下 DOM 元素作为this
是可用的。
Example 3.25. 通过选择器迭代
$('#myList li').each(function(idx, el) { console.log( 'Element ' + idx + 'has the following html: ' + $(el).html() ); });
一旦你指定了一个选择器, 乐趣就开始了。你能改变,移动,删除和克隆元素。你还能通过简单的语法创建一个新元素。
有关 jQuery 操作方法的完整文档,请访问 http://api.jquery.com/category/manipulation/.
这里有一些方法帮助你改变已有的元素。在多数通常的任务中,你能执行内部HTML或元素属性的改变。jQuery 为这些操作的有序提供了简单, 跨浏览器的方法。你也能在典型的getter中使用相同的方法获取元素信息。 我们要看到的例子将贯穿这一节,但是特殊的,这有一些你可以使用的新的方法用于获取和设置元素信息。
改变这些元素是琐碎的,但请记住这些改变将作用于选择器的 all 元素, 因此如果你打算改变一个元素,在调用setter方法前请确认你的选择器。
当方法作为getters使用时,它们通常只工作在选择器的第一个元素,并且不会返回 jQuery 对象,所以你不能给它们链式添加方法。一个值得注意的例外是 $.fn.text
; 描述如下, 它在选择器中获得所有元素的文档。
获取或设置html文档。
获取或设置text文档;HTML将被剥离。
获取和设置提供属性的值
获取或设置作为整型选择器的第一个元素的pixels宽度。
获取或设置作为整型选择器的第一个元素的pixels高度。
为选择器中的第一个元素取得包含位置信息的对象,相对于它们第一个位置的祖先。这只有一个getter
获取或设置form元素的值。
这里有一些移动DOM元素的方法;通常,这里有两个途径:
放置与另外元素有关的选择元素Place the selected element(s) relative to another element
放置一个与选择元素有关的元素Place an element relative to the selected element(s)
在例子中, jQuery 提供了 $.fn.insertAfter
和
$.fn.after
. $.fn.insertAfter
方法在你提供了参数的元素后面放置一个选择元素;$.fn.after
方法在选择元素后面放置一个带参数的元素。其它的方法允许这样的模式:$.fn.insertBefore
and
$.fn.before
; $.fn.appendTo
and
$.fn.append
; and $.fn.prependTo
and
$.fn.prepend
.
这个方法为你进行的更多检测将依赖于那些你已经选择的元素,是否你将需要保存那些你加入到页面的元素引用。如果你需要保存引用,你也将生效第一次处理 -- 放置与其它元素有关的选择元素 -- 在这个例子中,作为你放置元素的返回值。
$.fn.insertAfter
, $.fn.insertBefore
,
$.fn.appendTo
, and $.fn.prependTo
will be your
tools of choice.
Example 3.27. 使用不同的方法移动元素
// 将第一个列表项作为最后一项 make the first list item the last list item var $li = $('#myList li:first').appendTo('#myList'); // 同一问题的另一种处理 $('#myList').append($('#myList li:first')); // 注意这没有方法访问 // 我们移动的列表项, // 返回列表自己
当你使用 $.fn.appendTo 方法时, 你将移动元素;有时你想要以拷贝元素替代。在这种情况下, 你将首先使用 $.fn.clone 。
如果你需要拷贝相关数据和事件,请确定传递 true
参数到 $.fn.clone
。
这里有两种方法将元素从页面删除:
$.fn.remove
和 $.fn.detach
。当你打算把选择器从页面永久删除时你将使用 $.fn.remove
;尽管方法返回删除的元素,那些元素也不会拥有它们关联的数据和事件。
如果你需要将数据和事件的持久性,你需要使用 $.fn.detach
来代替。就像 $.fn.remove
, 它返回一个选择器, 也维护与选择有关的数据和事件,于是你能在最后恢复选择器。
如果你对一个元素做很多的操作,$.fn.detach
方法极有价值。在这个例子里,它有利于 $.fn.detach
页面元素,工作在它的代码里,于是当你操作时它将恢复到页面。把你从维护这些元素的数据和事件的高昂"DOM touches"中解救出来。
如果你要留下元素而又要简单的删除它们的内容,你能使用 $.fn.empty
去处理内部HTML元素。
jQuery 提供了一个简单而优雅的途径,使用与你选择器同样的方法 $()
来创建新元素。
Example 3.30. 创建一个包含属性对象的新元素
$('<a/>', { html : 'This is a <strong>new</strong> link', 'class' : 'new', href : 'foo.html' });
注意我们包含在第二个参数中的属性对象,属性名类被引用,而属性名的文本和链接却不是。属性名通常不被引用, 除非它们作为保留字(在这个例子中是作为类)。
当你创建一个新元素,它不会立刻添加到你的页面。一旦它被创建这里会有几个方法添加一个元素到页面里。
Example 3.31. 从页中获得一个新元素
var $myNewElement = $('<p>New element</p>'); $myNewElement.appendTo('#content'); $myNewElement.insertAfter('ul:last'); // 这将从 #content 中移除P! $('ul').last().after($myNewElement.clone()); // 克隆P现在我们有了两个
严格地讲,你不必在变量中保存创建的元素 -- 你能直接在$()后调用此方添加元素到页面。然后, 大多数时间你将引用添加的元素,而不需要在后面选择它。
你甚至能在添加元素的时候创建它,但这个例子请注意你不能获得新创建的元素的引用。
在页面创建新元素的语法非常简单,模版会忽略在反复添加到DOM时所产生的巨大性能代价。如果你添加更多的元素到同一容器里,你将连接所有的html到单一的字符串中,接着追加字符到容器里替换掉一次一个的元素追加方式。你能使用一个数组收集所有的片段,然后 join
它们追加到同一字符串中。
var myItems = [], $myList = $('#myList'); for (var i=0; i<100; i++) { myItems.push('<li>item ' + i + '</li>'); } $myList.append(myItems.join(''));
jQuery 属性操作的能力是很广泛的。基本的改变是很简单的,但 $.fn.attr 方法也允许完成更多复杂的操作。
Example 3.34. 操作多个属性
$('#myDiv a:first').attr({ href : 'newDestination.html', rel : 'super-special' });
Example 3.35. 使用函数去确定新的属性值
$('#myDiv a:first').attr({ rel : 'super-special', href : function() { return '/new/' + $(this).attr('href'); } }); $('#myDiv a:first').attr('href', function() { return '/new/' + $(this).attr('href'); });
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/sandbox.js
or
work in Firebug to accomplish the following:
Select all of the div elements that have a class of "module".
Come up with three selectors that you could use to get the third item in the #myList unordered list. Which is the best to use? Why?
Select the label for the search input using an attribute selector.
Figure out how many elements on the page are hidden (hint: .length).
Figure out how many image elements on the page have an alt attribute.
Select all of the odd table rows in the table body.
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/sandbox.js
or
work in Firebug to accomplish the following:
Select all of the image elements on the page; log each image's alt attribute.
Select the search input text box, then traverse up to the form and add a class to the form.
Select the list item inside #myList that has a class of "current" and remove that class from it; add a class of "current" to the next list item.
Select the select element inside #specials; traverse your way to the submit button.
Select the first list item in the #slideshow element; add the class "current" to it, and then add a class of "disabled" to its sibling elements.
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/sandbox.js
or work
in Firebug to accomplish the following:
Add five new list items to the end of the unordered list #myList. Hint:
for (var i = 0; i<5; i++) { ... }
Remove the odd list items
Add another h2 and another paragraph to the last div.module
Add another option to the select element; give the option the value "Wednesday"
Add a new div.module to the page after the last one; put a copy of one of the existing images inside of it.
直到现在,我们已经在 jQuery 对象调用上完全使用了此方法。例子:
$('h1').remove();
大部分对 jQuery 对象的方法调用都是这样的;这些方法表明自己是 $.fn
命名空间的一部分,或者是 “jQuery prototype,” 这是作为 jQuery 对象方法最好的思路。
然而,还有一些方法在选择器上不起作用;这些方法表明自己是 jQuery 命名空间的一部分,这是作为核心 jQuery 方法最好的思路。
这的区别对 jQuery 新手是难以分辨的。这的东西你需要记住:
jQuery 选择器用于 $.fn
命名空间,它会自动接收和返回所选择的。
$
命名空间里的方法通常是实用类型的方法,它们不会和选择器工作在一起;它们不会自动地处理任何参数,它们的返回值也将改变。
这里有一些对象方法和核心方法拥有同样名字的例子,例如 $.each
和 $.fn.each
。在这些例子中,要正确地研究这些方法需要你非常仔细地阅读文档。
jQuery 在 $
命名空间中提供了多个实用方法。这些方法能帮助你完成例程(routine programming)任务。接下来有一些实用方法的例子;完整的 jQuery 实用方法的参考, 请访问 http://api.jquery.com/category/utilities/.
去除行间和末尾的空白。
$.trim(' 大量额外空白 '); // 返回 '大量额外空白'
在数组和对象上迭代。
$.each([ 'foo', 'bar', 'baz' ], function(idx, val) { console.log('element ' + idx + 'is ' + val); }); $.each({ foo : 'bar', baz : 'bim' }, function(k, v) { console.log(k + ' : ' + v); });
还有一个方法 $.fn.each
,也可用于在一个选择的元素中进行迭代。
在数组中返回值索引,如果值不在数组中就为 -1 。
var myArray = [ 1, 2, 3, 5 ]; if ($.inArray(4, myArray) !== -1) { console.log('found it!'); }
使用后面对象的属性改变第一个对象的属性。
var firstObject = { foo : 'bar', a : 'b' }; var secondObject = { foo : 'baz' }; var newObject = $.extend(firstObject, secondObject); console.log(firstObject.foo); // 'baz' console.log(newObject.foo); // 'baz'
如果你不打算改变任何对象你可传递 $.extend
,作为第一个参数传递一个空对象。
var firstObject = { foo : 'bar', a : 'b' }; var secondObject = { foo : 'baz' }; var newObject = $.extend({}, firstObject, secondObject); console.log(firstObject.foo); // 'bar' console.log(newObject.foo); // 'baz'
返回一个函数它总是运行在一个提供的作用域里 — 就是说,this
的设置意味着内部传递函数到第二个参数。
var myFunction = function() { console.log(this); }; var myObject = { foo : 'bar' }; myFunction(); // 记录 window 对象 var myProxyFunction = $.proxy(myFunction, myObject); myProxyFunction(); // 记录 myObject 对象
如果你拥有一个带方法的对象,你能传递对象和方法名去返回一个总运行在对象作用域里的函数。
var myObject = { myFn : function() { console.log(this); } }; $('#foo').click(myObject.myFn); // 记录 DOM 元素 #foo $('#foo').click($.proxy(myObject, 'myFn')); // 记录 myObject
在 "JavaScript basics" 章节被提及,jQuery 提供一些基础的实用方法去检测特定值的类型。
Example 4.1. 校验任意值的类型e
var myValue = [1, 2, 3]; // 使用 JavaScript 的 typeof 运算符去测试类型 typeof myValue == 'string'; // false typeof myValue == 'number'; // false typeof myValue == 'undefined'; // false typeof myValue == 'boolean'; // false // 使用严格的相等运算符去测试 null myValue === null; // false // 使用 jQuery 方法去测试 jQuery.isFunction(myValue); // false jQuery.isPlainObject(myValue); // false jQuery.isArray(myValue); // true
作为你在 jQuery 的工作进展, As your work with jQuery progresses, 你将发现这里常有一些你准备作为元素要存储的数据。you'll find that there's often data about an element that you want to store with the element. 在 JavaScript 里,In plain JavaScript, 你可能直接将属性加入到 DOM 元素中,you might do this by adding a property to the DOM element, 但是你必须处理在一些浏览器中的内存泄露。but you'd have to deal with memory leaks in some browsers. jQuery 提供了一个简洁的方法存储相关数据到元素中,它帮你管理内存问题。offers a straightforward way to store data related to an element, and it manages the memory issues for you.
Example 4.2. 存储和检索一个元素的相关数据
$('#myDiv').data('keyName', { foo : 'bar' }); $('#myDiv').data('keyName'); // { foo : 'bar' }
你能存储一个元素任何种类的数据,当你进行一个复杂应用开发时它们的重要性难以用语言表达
。这一课的目的,我们将使用 $.fn.data
来存储其它元素的引用。
在这的例子中,我们可能需要在一组列表项和它的内部划分上建立一种关系。我们建立的这个关系在每一个单独时间里与这些条目相互作用,但是一个更好的解决方案是一旦这个关系创建好,通过使用$.fn.data
: 来存储划分条目的指针and then store a pointer to
the div on the list item using $.fn.data
:
Example 4.3. 使用 $.fn.data
存储元素之间的关系
$('#myList li').each(function() { var $li = $(this), $div = $li.find('div.content'); $li.data('contentDiv', $div); }); // 后来,我们不得不再一次发现 div; // 我们能从列表项中读取到它 var $firstLi = $('#myList li:first'); $firstLi.data('contentDiv').html('new content');
除了传递 $.fn.data
一个 key-value 对到存储数据外,你也能传递一个包含一个或更多键值对的对象。
尽管 jQuery 消除了大部分 JavaScript 在浏览器间的差异性,这里仍有很多场合是你的代码需要知道浏览器的环境 there are still occasions when your code needs to know about the browser environment.
jQuery 提供的 $.support
对象, 和 $.browser
对象一样被废弃。关于这些对象的完整文档,请访问 http://api.jquery.com/jQuery.support/
和 http://api.jquery.com/jQuery.browser/.
$.support
对象主要用于检测浏览器所支持的功能;在为不同浏览器定制 JavaScript 代码时它被推荐作为“future-proof”方法来使用。
虽然不赞成使用 $.browser
对象而鼓励使用 $.support
对象,但它仍保留在 jQuery 中而没删除。它提供了直接检测浏览器类型和版本的功能。
如果你在其它 JavaScript 库中实用了$
变量,你会与 jQuery 发生冲突。为了避免这样的冲突,在你准备实用 jQuery 之前和将 jQuery 加载到你的页面后请你将 jQuery 设为 no-conflict 模式。
当你把 jQuery 设为 no-conflict 模式时,你有替代 $
的变量名所分配的选项。
Example 4.4. 将 jQuery 设为 no-conflict 模式
<script src="prototype.js"></script> <script src="jquery.js"></script> <script>var $j = jQuery.noConflict();</script>
你能通过在自执行的匿名函数中封装你的代码而继续使用标准的 $
;这是插件开发的一个标准模式,在别的地方作者不知道是否有别的库已经使用了 $
。
Example 4.5. 在自执行匿名函数中使用 $
<script src="prototype.js"></script> <script src="jquery.js"></script> <script> jQuery.noConflict(); (function($) { // 你的代码在这,它使用 $ })(jQuery); </script>
jQuery提供了简单的方法为元素绑定事件处理函数。 当事件发生的时候,所提供的函数将会执行。 函数的内容取决于被点击的元素。
想要获得关于jQuery事件的详细内容请访问http://api.jquery.com/category/events/。
事件处理的函数可以接收事件对象为参数。 事件对象可以用来判断事件的类型以达到阻止该事件的默认动作的目的
想要获得关于事件对象的详细内容请访问http://api.jquery.com/category/events/event-object/。
jQuery为常用的事件提供了简便的绑定方法,这些方法是日后最常用的方法。
这些方法是jQuery的$.fn.bind
方法的简便缩写,
例如$.fn.click
、$.fn.focus
、$.fn.blur
、$.fn.change
等。
bind这个方法一般用于绑定相同的事件处理函数到多个事件,
也用于为事件处理函数提供数据,或者在使用自定义事件时使用。
Example 5.3. 使用$.fn.bind
方法将数据绑定到事件
$('input').bind( 'click change', // 绑定到多个事件 { foo : 'bar' }, // 传递数据 function(eventObject) { console.log(eventObject.type, eventObject.data); // 输出事件类型和{ foo : 'bar' } logs event type, then { foo : 'bar' } } );
有时候你需要一个只执行一次的特殊处理函数,或者不希望有处理函数运行,或者需要运行另一个不同的处理函数。
jQuery为此提供了$.fn.one
方法。
Example 5.4. 使用$.fn.one
方法转换处理函数
$('p').one('click', function() { console.log('You just clicked this for the first time!'); $(this).click(function() { console.log('You have clicked this before!'); }); });
如果你想只在某个元素第一次被点击的时候而不是之后的每次都做一些复杂的初始化,
$.fn.one
方法是很好的选择。
你可以使用$.fn.unbind
方法,通过传递事件类型为参数的方式取消事件处理函数的绑定。
如果你在事件中附加了已命名的函数,你可以通过将这个命名函数作为第二个参数传到
$.fn.unbind
方法中来单独取消绑定。
Example 5.6. 取消特定点击事件处理函数绑定
var foo = function() { console.log('foo'); }; var bar = function() { console.log('bar'); }; $('p').bind('click', foo).bind('click', bar); $('p').unbind('click', bar); // foo依然被绑定到点击事件
就如在概述中提到的一样,事件处理函数可以接收包含很多属性和方法的事件对象。 事件对象常使用preventDefault方法阻止事件默认动作的执行。 当然,事件对象还包含了其它的有用的属性和方法,包括:
事件触发时鼠标相对于页面左上角的位置。
事件的类型(例如“click”)。
按下的按钮或按键。
事件被绑定时传入的所有数据。
初始化事件的DOM元素。
阻止事件默认方法的执行(例如点击一个链接)。
停止事件影响其它元素。
除了事件对象之外,事件处理函数也可以通过this
关键字访问事件处理函数所绑定的DOM元素。
我们可以使用$(this)
将DOM元素转换成可以使用jQuery方法的jQuery对象,语法如下:
var $this = $(this);
Example 5.8. 阻止链接被访问
$('a').click(function(e) { var $this = $(this); if ($this.attr('href').match('evil')) { e.preventDefault(); $this.addClass('evil'); } });
jQuery提供了$.fn.trigger
方法在没有用户交互的情况下触发绑定到元素的事件处理函数。
当然这个方法还有自己用途,而不仅仅是被用来调用一个被绑定为点击事件处理的函数。
相反的,应该把需要调用的函数存储到变量里,当绑定的时候将变量名传递到方法里。
这样你可以在任何时候不使用$.fn.trigger
直接调用这个函数。
Example 5.9. 以正确的方式触发事件处理函数
var foo = function(e) { if (e) { console.log(e); } else { console.log('this didn\'t come from an event!'); } }; $('p').click(foo); foo(); // 不使用$('p').trigger('click')直接调用
你将会频繁地使用jQuery添加新的元素到页面中,你将有可能需要绑定一些已经存在在页面上的事件到这些新元素上。
相对于在为页面添加元素的时候重复绑定事件,你可以使用事件委托。
通过事件委托将事件绑定到容器元素,当事件被触发的时候,可以知道事件是在哪个容器元素被触发。
如果这听起来很复杂,幸运地jQuery提供了$.fn.live
和$.fn.delegate
方法让这个过程变得很简单。
当大多数的人在处理后来才被添加到页面的元素时才使用委托,就算你从不添加更多的元素到页面,委托也有性能上的优势。 绑定事件处理器到成百上千的不同元素所需的时间并不是小数目,如果你有很多元素,你应该考虑将相关的事件委托到一个容器元素中。
从jQuery 1.3开始提供了$.fn.live
方法,在1.3版本这个方法只支持特定的事件类型。
自从jQuery 1.4.2开始提供$.fn.delegate
方法,应优先选择这个方法。
Example 5.10. 使用$.fn.delegate
委托事件
$('#myUnorderedList').delegate('li', 'click', function(e) { var $myListItem = $(this); // ... });
Example 5.11. 使用$.fn.live
委托事件
$('#myUnorderedList li').live('click', function(e) { var $myListItem = $(this); // ... });
jQuery提供了两个相关事件助手函数让你减少打字次数。
$.fn.hover
方法让一个或两个函数在mouseenter
和mouseleave
事件触发的时候执行。
如果你传入一个函数,它将在两个事件触发的时候都执行;如果传入两个函数,第一个函数将在mouseenter
事件触发的时候执行,
第二个函数在mouseleave
事件触发的时候执行。
在jQuery 1.4之前,$.fn.hover
方法需要两个函数。
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/inputHint.js
or
work in Firebug. Your task is to use the text of the label for the search
input to create "hint" text for the search input. The steps are as
follows:
Set the value of the search input to the text of the label element
Add a class of "hint" to the search input
Remove the label element
Bind a focus event to the search input that removes the hint text and the "hint" class
Bind a blur event to the search input that restores the hint text and "hint" class if no search text was entered
What other considerations might there be if you were creating this functionality for a real site?
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/tabs.js
. Your
task is to create tabbed navigation for the two div.module elements. To
accomplish this:
Hide all of the modules.
Create an unordered list element before the first module.
Iterate over the modules using $.fn.each
. For each
module, use the text of the h2 element as the text for a list item
that you add to the unordered list element.
Bind a click event to the list item that:
Shows the related module, and hides any other modules
Adds a class of "current" to the clicked list item
Removes the class "current" from the other list item
Finally, show the first tab.
jQuery可以很容易让你添加简单的特效到你的页面。效果 可以用基本设置,也可以自定义持续时间,甚至 还可以执行一个CSS属性设置的自定义动画。
关于这章节更加完整的细节,请参考http://api.jquery.com/category/effects/.
jquery常用的基本效果方法:
显示匹配的元素
隐藏匹配的元素
通过淡入的方式显示匹配元素
通过淡出的方式显示匹配元素
通过高度变化(向下增大)用滑动动画显示一个匹配元素
通过高度变化(向上减小)用滑动动画隐藏一个匹配元素
根据当前的元素display属性,用滑动动画显示或隐藏一个匹配元素
$.fn.show
和
$.fn.hide
, 除此而外,所有的基本方法是在默认情况下,都是400毫秒延时的动画效果。改变duration属性(一个效果持续时间)是很简单。
Example 6.2. 设置动画效果持续时间
$('h1').fadeIn(300); // 淡入效果以 300ms延时 $('h1').fadeOut('slow'); // using a built-in speed definition使用慢的淡出效果
通常,一旦完成动画你会想运行一些代码 - 如果你是在做动画之前运行它,可能就会影响动画的效果,也可能会删除动画的一部分元素。
[Definition: 回调函数回调函数提供一个方式去注册你感兴趣的将要发生的事件。
]在这种情况下,我们将响应动画的事件 this
是存在动画的DOM元素,如果多个元素一起做动画效果,值得注意的是这个回调函数在每个匹配元素上只调用一次,不是这个动画作为一个整体 。(这个地方没翻译对。。。。。。。。。。)
注意,如果您选择不返回任何元素,您的 回调将不会执行!你可以通过测试是否选择任何元素来解决这个问题;如果没有, 你可以立即执行回调。
Example 6.5. 运行回调函数即使没设置任何参数
var $thing = $('#nonexistent'); var cb = function() { console.log('done!'); }; if ($thing.length) { $thing.fadeIn(300, cb); } else { cb(); }
jQuery 使得通过$.fn.animate
方法设置CSS属性控制动画成为可能。
$.fn.animate
这个方法允许你设置一个值,或者使用默认值。
Example 6.6. 使用$.fn.animate
自定义效果
$('div.funtimes').animate( { left : "+=50", opacity : 0.25 }, 300, // 持续时间 function() { console.log('done!'); // 回调 });
颜色相关的属性是不能通过$.fn.animate
用在jQuery以外的。color
plugin可以很轻松完成颜色动画。我们将在书的后面章节讨论如何使用此插件。
[Definition: Easing Easing描述:出现在其中一个效果 - 无论是变化或是稳定的,或动画进行中在不同点位的速度。 ]jQuery包括两个easing缓冲方法:"linear" 和 "swing"。如果你想在你的动画更加自然的过渡,各种easing插件就可以用上。
在jQuery 1.4 版本中,我们使用$.fn.animate方法能容易的设置 per-property 。
Example 6.7. easing属性
$('div.funtimes').animate( { left : [ "+=50", "swing ], opacity : [ 0.25, "linear" ] }, 300 );
关于easing选项的更多细节,请参见 http://api.jquery.com/animate/.
jQuery提供了几种方法来设置动画。
停止运行当前匹配元素上的动画
在运行下一个动画前等待指定的毫秒数
$('h1').show(300).delay(1000).hide(300);
当这个属性设置为true的时候,这个动画将不被转换;元素将立刻设置为它们的最终状态。这在旧 浏览器中特别有用;你也可以为你的用户提供这些选项。
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/blog.js
. Your
task is to add some interactivity to the blog section of the page. The
spec for the feature is as follows:
Clicking on a headline in the #blog div should slide down the excerpt paragraph
Clicking on another headline should slide down its excerpt paragraph, and slide up any other currently showing excerpt paragraphs.
Hint: don't forget about the :visible
selector!
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/navigation.js
.
Your task is to add dropdowns to the main navigation at the top of the
page.
Hovering over an item in the main menu should show that item's submenu items, if any.
Exiting an item should hide any submenu items.
To accomplish this, use the $.fn.hover
method to add
and remove a class from the submenu items to control whether they're
visible or hidden. (The file at
/exercises/css/styles.css
includes the "hover" class
for this purpose.)
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/slideshow.js
. Your
task is to take a plain semantic HTML page and enhance it with JavaScript by
adding a slideshow.
Move the #slideshow element to the top of the body.
Write code to cycle through the list items inside the element; fade one in, display it for a few seconds, then fade it out and fade in the next one.
When you get to the end of the list, start again at the beginning.
For an extra challenge, create a navigation area under the slideshow that shows how many images there are and which image you're currently viewing. (Hint: $.fn.prevAll will come in handy for this.)
The XMLHttpRequest method (XHR) allows browsers to communicate with the server without requiring a page reload. This method, also known as Ajax (Asynchronous JavaScript and XML), allows for web pages that provide rich, interactive experiences.
Ajax requests are triggered by JavaScript code; your code sends a request to a URL, and when it receives a response, a callback function can be triggered to handle the response. Because the request is asynchronous, the rest of your code continues to execute while the request is being processed, so it’s imperative that a callback be used to handle the response.
jQuery provides Ajax support that abstracts away painful browser
differences. It offers both a full-featured $.ajax()
method,
and simple convenience methods such as $.get()
,
$.getScript()
, $.getJSON()
,
$.post()
, and $().load()
.
Most jQuery applications don’t in fact use XML, despite the name “Ajax”; instead, they transport data as plain HTML or JSON (JavaScript Object Notation).
In general, Ajax does not work across domains. Exceptions are services that provide JSONP (JSON with Padding) support, which allow limited cross-domain functionality.
Proper use of Ajax-related jQuery methods requires understanding some key concepts first.
The two most common “methods” for sending a request to a server are GET and POST. It’s important to understand the proper application of each.
The GET method should be used for non-destructive operations — that is, operations where you are only “getting” data from the server, not changing data on the server. For example, a query to a search service might be a GET request. GET requests may be cached by the browser, which can lead to unpredictable behavior if you are not expecting it. GET requests generally send all of their data in a query string.
The POST method should be used for destructive operations — that is, operations where you are changing data on the server. For example, a user saving a blog post should be a POST request. POST requests are generally not cached by the browser; a query string can be part of the URL, but the data tends to be sent separately as post data.
jQuery generally requires some instruction as to the type of data you expect to get back from an Ajax request; in some cases the data type is specified by the method name, and in other cases it is provided as part of a configuration object. There are several options:
For transporting simple strings
For transporting blocks of HTML to be placed on the page
For adding a new script to the page
For transporting JSON-formatted data, which can include strings, arrays, and objects
As of jQuery 1.4, if the JSON data sent by your server isn't properly formatted, the request may fail silently. See http://json.org for details on properly formatting JSON, but as a general rule, use built-in language methods for generating JSON on the server to avoid syntax issues.
For transporting JSON data from another domain
For transporting data in a custom XML schema
I am a strong proponent of using the JSON format in most cases, as it provides the most flexibility. It is especially useful for sending both HTML and data at the same time.
The asynchronicity of Ajax catches many new jQuery users off guard. Because Ajax calls are asynchronous by default, the response is not immediately available. Responses can only be handled using a callback. So, for example, the following code will not work:
var response;
$.get('foo.php', function(r) { response = r; });
console.log(response); // undefined!
Instead, we need to pass a callback function to our request; this callback will run when the request succeeds, at which point we can access the data that it returned, if any.
$.get('foo.php', function(response) { console.log(response); });
In general, Ajax requests are limited to the same protocol (http or https), the same port, and the same domain as the page making the request. This limitation does not apply to scripts that are loaded via jQuery's Ajax methods.
The other exception is requests targeted at a JSONP service on
another domain. In the case of JSONP, the provider of the service has
agreed to respond to your request with a script that can be loaded into
the page using a <script>
tag, thus avoiding the
same-origin limitation; that script will include the data you requested,
wrapped in a callback function you provide.
Firebug (or the Webkit Inspector in Chrome or Safari) is an invaluable tool for working with Ajax requests. You can see Ajax requests as they happen in the Console tab of Firebug (and in the Resources > XHR panel of Webkit Inspector), and you can click on a request to expand it and see details such as the request headers, response headers, response content, and more. If something isn't going as expected with an Ajax request, this is the first place to look to track down what's wrong.
While jQuery does offer many Ajax-related convenience methods, the
core $.ajax
method is at the heart of all of them, and
understanding it is imperative. We'll review it first, and then touch
briefly on the convenience methods.
I generally use the $.ajax method and do not use convenience methods. As you'll see, it offers features that the convenience methods do not, and its syntax is more easily understandable, in my opinion.
jQuery’s core $.ajax
method is a powerful and
straightforward way of creating Ajax requests. It takes a configuration
object that contains all the instructions jQuery requires to complete
the request. The $.ajax
method is particularly valuable
because it offers the ability to specify both success and failure
callbacks. Also, its ability to take a configuration object that can be
defined separately makes it easier to write reusable code. For complete
documentation of the configuration options, visit http://api.jquery.com/jQuery.ajax/.
Example 7.1. Using the core $.ajax method
$.ajax({ // the URL for the request url : 'post.php', // the data to send // (will be converted to a query string) data : { id : 123 }, // whether this is a POST or GET request type : 'GET', // the type of data we expect back dataType : 'json', // code to run if the request succeeds; // the response is passed to the function success : function(json) { $('<h1/>').text(json.title).appendTo('body'); $('<div class="content"/>') .html(json.html).appendTo('body'); }, // code to run if the request fails; // the raw request and status codes are // passed to the function error : function(xhr, status) { alert('Sorry, there was a problem!'); }, // code to run regardless of success or failure complete : function(xhr, status) { alert('The request is complete!'); } });
A note about the dataType
setting: if the server
sends back data that is in a different format than you specify, your
code may fail, and the reason will not always be clear, because the
HTTP response code will not show an error. When working with Ajax
requests, make sure your server is sending back the data type you're
asking for, and verify that the Content-type header is accurate for
the data type. For example, for JSON data, the Content-type header
should be application/json
.
There are many, many options for the $.ajax method, which is part of its power. For a complete list of options, visit http://api.jquery.com/jQuery.ajax/; here are several that you will use frequently:
Set to false
if the request should be sent
synchronously. Defaults to true
. Note that if you
set this option to false, your request will block execution of
other code until the response is received.
Whether to use a cached response if available. Defaults to
true
for all dataTypes except "script" and "jsonp".
When set to false, the URL will simply have a cachebusting
parameter appended to it.
A callback function to run when the request is complete, regardless of success or failure. The function receives the raw request object and the text status of the request.
The scope in which the callback function(s) should run
(i.e. what this
will mean inside the callback
function(s)). By default, this
inside the callback
function(s) refers to the object originally passed to
$.ajax
.
The data to be sent to the server. This can either be an
object or a query string, such as
foo=bar&baz=bim
.
The type of data you expect back from the server. By default, jQuery will look at the MIME type of the response if no dataType is specified.
A callback function to run if the request results in an error. The function receives the raw request object and the text status of the request.
The callback name to send in a query string when making a JSONP request. Defaults to "callback".
A callback function to run if the request succeeds. The function receives the response data (converted to a JavaScript object if the dataType was JSON), as well as the text status of the request and the raw request object.
The time in milliseconds to wait before considering the request a failure.
Set to true to use the param serialization style in use prior to jQuery 1.4. For details, see http://api.jquery.com/jQuery.param/.
The type of the request, "POST" or "GET". Defaults to "GET". Other request types, such as "PUT" and "DELETE" can be used, but they may not be supported by all browsers.
The URL for the request.
The url
option is the only required property of the
$.ajax
configuration object; all other properties are
optional.
If you don't need the extensive configurability of
$.ajax
, and you don't care about handling errors, the Ajax
convenience functions provided by jQuery can be useful, terse ways to
accomplish Ajax requests. These methods are just "wrappers" around the
core $.ajax
method, and simply pre-set some of the options
on the $.ajax
method.
The convenience methods provided by jQuery are:
Perform a GET request to the provided URL.
Perform a POST request to the provided URL.
Add a script to the page.
Perform a GET request, and expect JSON to be returned.
In each case, the methods take the following arguments, in order:
The URL for the request. Required.
The data to be sent to the server. Optional. This can either
be an object or a query string, such as
foo=bar&baz=bim
.
This option is not valid for
$.getScript
.
A callback function to run if the request succeeds. Optional. The function receives the response data (converted to a JavaScript object if the data type was JSON), as well as the text status of the request and the raw request object.
The type of data you expect back from the server. Optional.
This option is only applicable for methods that don't already specify the data type in their name.
Example 7.2. Using jQuery's Ajax convenience methods
// get plain text or html $.get('/users.php', { userId : 1234 }, function(resp) { console.log(resp); }); // add a script to the page, then run a function defined in it $.getScript('/static/js/myScript.js', function() { functionFromMyScript(); }); // get JSON-formatted data from the server $.getJSON('/details.php', function(resp) { $.each(resp, function(k, v) { console.log(k + ' : ' + v); }); });
The $.fn.load
method is unique among jQuery’s Ajax
methods in that it is called on a selection. The $.fn.load
method fetches HTML from a URL, and uses the returned HTML to populate
the selected element(s). In addition to providing a URL to the method,
you can optionally provide a selector; jQuery will fetch only the
matching content from the returned HTML.
Example 7.4. Using $.fn.load
to populate an element based on a
selector
$('#newContent').load('/foo.html #myDiv h1:first', function(html) {
alert('Content updated!');
});
jQuery’s ajax capabilities can be especially useful when dealing
with forms. The jQuery
Form Plugin is a well-tested tool for adding Ajax capabilities to
forms, and you should generally use it for handling forms with Ajax rather
than trying to roll your own solution for anything remotely complex. That
said, there are a two jQuery methods you should know that relate to form
processing in jQuery: $.fn.serialize
and
$.fn.serializeArray
.
Example 7.6. Creating an array of objects containing form data
$('#myForm').serializeArray(); // creates a structure like this: [ { name : 'field1', value : 123 }, { name : 'field2', value : 'hello world' } ]
The advent of JSONP -- essentially a consensual cross-site scripting hack -- has opened the door to powerful mashups of content. Many prominent sites provide JSONP services, allowing you access to their content via a predefined API. A particularly great source of JSONP-formatted data is the Yahoo! Query Language, which we'll use in the following example to fetch news about cats.
Example 7.7. Using YQL and JSONP
$.ajax({ url : 'http://query.yahooapis.com/v1/public/yql', // the name of the callback parameter, // as specified by the YQL service jsonp : 'callback', // tell jQuery we're expecting JSONP dataType : 'jsonp', // tell YQL what we want and that we want JSON data : { q : 'select title,abstract,url from search.news where query="cat"', format : 'json' }, // work with the response success : function(response) { console.log(response); } });
jQuery handles all the complex aspects of JSONP behind-the-scenes -- all we have to do is tell jQuery the name of the JSONP callback parameter specified by YQL ("callback" in this case), and otherwise the whole process looks and feels like a normal Ajax request.
Often, you’ll want to perform an operation whenever an Ajax requests starts or stops, such as showing or hiding a loading indicator. Rather than defining this behavior inside every Ajax request, you can bind Ajax events to elements just like you'd bind other events. For a complete list of Ajax events, visit http://docs.jquery.com/Ajax_Events.
Example 7.8. Setting up a loading indicator using Ajax Events
$('#loading_indicator') .ajaxStart(function() { $(this).show(); }) .ajaxStop(function() { $(this).hide(); });
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/load.js
. Your task
is to load the content of a blog item when a user clicks on the title of the
item.
Create a target div after the headline for each blog post and
store a reference to it on the headline element using
$.fn.data
.
Bind a click event to the headline that will use the
$.fn.load
method to load the appropriate content from
/exercises/data/blog.html
into the target div.
Don't forget to prevent the default action of the click event.
Note that each blog headline in index.html includes a link to the
post. You'll need to leverage the href of that link to get the proper
content from blog.html. Once you have the href, here's one way to process it
into an ID that you can use as a selector in $.fn.load
:
var href = 'blog.html#post1'; var tempArray = href.split('#'); var id = '#' + tempArray[1];
Remember to make liberal use of console.log
to make sure
you're on the right path!
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/specials.js
. Your
task is to show the user details about the special for a given day when
the user selects a day from the select dropdown.
Append a target div after the form that's inside the #specials element; this will be where you put information about the special once you receive it.
Bind to the change event of the select element; when the user
changes the selection, send an Ajax request to
/exercises/data/specials.json
.
When the request returns a response, use the value the user
selected in the select (hint: $.fn.val
) to look up
information about the special in the JSON response.
Add some HTML about the special to the target div you created.
Finally, because the form is now Ajax-enabled, remove the submit button from the form.
Note that we're loading the JSON every time the user changes their selection. How could we change the code so we only make the request once, and then use a cached response when the user changes their choice in the select?
jQuery 插件简单的就是一个用于扩展 jQuery 的 prototype 对象的新方法。
通过扩展 jQuery 的 prototype 对象,所有 jQuery 对象都将通过继承获得你添加的新方法。
我们知道,调用 jQuery()
时实际上是创建了一个继承了 jQuery
所有方法的全新对象。
插件的基本思路就是对一组元素执行一些操作。
你可以认为 jQuery 核心对象上的那些方法就是插件,比如 fadeOut
和 addClass
。
你可以创建属于你自己的插件,私底下用用或者发布出去。 网上已有数千款 jQuery 插件之多。 创建一个属于自己的插件并不是那么难的,是不是恨不得马上开始呢?
一个典型的插件的代码如下:
(function($){ $.fn.myNewPlugin = function() { return this.each(function(){ // 一堆代码... }); }; }(jQuery));
那几行代码,没把你搞晕吧? 其实里面的重点就是扩展 jQuery 的 prototype 对象,看下面这行:
$.fn.myNewPlugin = function() { //...
这里只是将它放在一个即时执行的函数内部:
(function($){ //... }(jQuery));
这样做是为了营造一个私有的 scope,我们可以在这个私有 scope 内部安全地使用 $ 扩展 jQuery, 而不用担心 $ 会被其他 js 库的改写。
所以,这里实际的插件代码是这样的:
$.fn.myNewPlugin = function() { return this.each(function(){ // 一堆代码... }); };
新插件中,关键字 this
指向的是调用这个插件的 jQuery 对象。
var somejQueryObject = $('#something'); $.fn.myNewPlugin = function() { alert(this === somejQueryObject); }; somejQueryObject.myNewPlugin(); // alerts 'true'
典型的 jQuery 对象是包含任意多个的 DOM 对象的, 这也是为什么 jQuery 对象被看作是 DOM 对象集的原因。
因此,针对对象集做操作需要遍历它,
通过 jQuery 的 each()
方法我们很容易地实现了。
$.fn.myNewPlugin = function() { return this.each(function(){ }); };
与 jQuery 其它方法一样,each()
方法也是返回一个 jQuery 对象,
这成就了我们所熟知并深爱的链式写法($(...).css().attr()...
)。
我们不应该破坏这种惯例,于是乎,我们也应该返回一个 this
对象。
在循环内部,你可以对每一个元素做你要做的事。就上面所讨论的,
下面是一个小插件的例子。
(function($){ $.fn.showLinkLocation = function() { return this.filter('a').each(function(){ $(this).append( ' (' + $(this).attr('href') + ')' ); }); }; }(jQuery)); // 使用样例: $('a').showLinkLocation();
这个小巧的插件可以将集合中所有的链接的 href
属性值包含在括号
中并将它作为该元素的内容显示出来。
<!-- 调用插件之前: --> <a href="page.html">Foo</a> <!-- 之后: --> <a href="page.html">Foo (page.html)</a>
虽然简单,但这个插件还是可以优化一下:
(function($){ $.fn.showLinkLocation = function() { return this.filter('a').append(function(){ return ' (' + this.href + ')'; }); }; }(jQuery));
我们利用了,append
方法是可以接受一个回调函数作为参数,
并将回调函数的返回值作为要添加的内容的这一点。同时,也可以注意到,
我们不是通过 attr
方法去获取 href
的属性值,
而是直接使用了内置的 DOM API,访问元素的 href
属性来获取。
下面是另一个例子。它不需要我们用 each()
方法去循环遍历,
只是简单地直接委派给另一个 jQuery 方法:
(function($){ $.fn.fadeInAndAddClass = function(duration, className) { return this.fadeIn(duration, function(){ $(this).addClass(className); }); }; }(jQuery)); // 使用方法: $('a').fadeInAndAddClass(400, 'finishedFading');
通过插件的方式扩展 jQuery 的基础功能, jQuery 的插件体系也成为它其中一个最著名的方面。从表格排序到表单校验, 再到自动补完功能……,如果有这些方面的需求, 那么好大机会已经有人写好相应的插件了。
虽然数量众多,但是插件的质量却不那么一致。有很多插件是经过大量的测试并且 有很好的维护,但有些则完成得很匆忙,会慢慢地远离人们的视线。 有不少并不符合最佳实践的要求。
寻找 jQuery 插件,最好的办法还是 Google。 不过,jQuery 开发组正在努力建设一个更好的插件库。如果你通过 Google 搜索出一些插件, 你可以通过邮件列表或者 #jquery IRC channel 向其他人咨询一下。
当你要找一个插件来满足需求时,多做点功课,尽可能找那些文档写得清楚点的, 可以让作者提供一堆用法例子的。需要注意哪些大大超出需求要求的插件, 它们可能带来很多没必要的开销。要了解更多如何觅得所爱,可以看看 Remy Sharp 的 Signs of a poorly written jQuery plugin。
选好插件之后就可以将它加入到页面中去了。先下载,需要解压就解压, 将插件脚本放在应用所在的目录中,再通过 script 标签(放置于 jQuery 之后) 将插件引入到页面中去。
有时候,你的代码中需要一块功能的支持,比如, 你希望用一个方法将对 jQuery 对象的一长串操作封装起来, 那么,你会需要一个自己编写的插件。
大部分插件都是简单地在 $.fn
名字空间下添加方法。
jQuery 确保了在调用的方法内部 this
所指向的就是调用该方法的 jQuery 对象。
反过来,你的插件也应该确保返回同一的对象,除非是在文档里明确声明了。
下面是一个简单插件的例子:
Example 8.1. 这个插件用于 hover(鼠标划过)时添加/删除一个 class
// 插件定义 (function($){ $.fn.hoverClass = function(c) { return this.hover( function() { $(this).toggleClass(c); } ); }; }(jQuery); // 插件使用样例 $('li').hoverClass('hover');
想要了解更多插件开发的内容,可以阅读 Mike Alsup 撰写的 A
Plugin Development Pattern。文中讲述了他开发的一个叫 $.fn.hilight
的插件,
用于对在有 metadata 插件时提供一些支持,和一个统一设置插件的全局和实例参数的方法。
Example 8.2. Mike Alsup 的 jQuery 插件设计模式
// // create closure // (function($) { // // plugin definition // $.fn.hilight = function(options) { debug(this); // build main options before element iteration var opts = $.extend({}, $.fn.hilight.defaults, options); // iterate and reformat each matched element return this.each(function() { $this = $(this); // build element specific options var o = $.meta ? $.extend({}, opts, $this.data()) : opts; // update element styles $this.css({ backgroundColor: o.background, color: o.foreground }); var markup = $this.html(); // call our format function markup = $.fn.hilight.format(markup); $this.html(markup); }); }; // // private function for debugging // function debug($obj) { if (window.console && window.console.log) window.console.log('hilight selection count: ' + $obj.size()); }; // // define and expose our format function // $.fn.hilight.format = function(txt) { return '<strong>' + txt + '</strong>'; }; // // plugin defaults // $.fn.hilight.defaults = { foreground: 'red', background: 'yellow' }; // // end of closure // })(jQuery);
这一章节的内容是基于 Scott Gonzalez 一篇博客 Building Stateful jQuery Plugins(已获作者许可)
虽然大多数的 jQuery 插件都是无状态的(stateless),也就是说, 与插件进行交互的就限于调用插件时的那一组对象, 但是有好大一部分功能需求没办法通过这种简单的插件模式来实现。
为了填补这一空白,jQuery UI 实现一套更加先进的插件系统。
它可以管理状态,允许通过一个插件暴露多个函数,并提供多个扩展点。
这套系统被称为 widget factory,对应 jQuery.widget
,
也是 jQuery UI 1.8 的一部分。不过,它是可以独立于 jQuery UI 使用的。
我们接下来创建一个简单的进度条插件,用来演示 widget factory 的能力。
我们首先创建一个只能设置一次的进度条。
下面是实现代码,使用 jQuery.widget
创建一个插件。
它接受两个参数,插件名字和带有具体实现方法的对象。
当插件被调用时,它会创建一个新的插件实例,而插件方法的执行对象也就是那个实例。
这与标准 jQuery 插件实现有两点是很不一样的。一是,执行者是对象而不是 DOM 元素;
二是,执行者永远是单个对象,而不是元素集。
Example 8.3. 用 jQuery UI widget factory 创建一个简单的有状态的插件
$.widget("nmk.progressbar", { _create: function() { var progress = this.options.value + "%"; this.element .addClass("progressbar") .text(progress); } });
插件名字必须包含一个命名空间,这里我们用了 nmk
这个命名空间。
但这个命名空间有个限制——只允许一层,也就是说,我们不能使用像
nmk.foo
这样的命名空间。另外可以看到 widget factory
给我们提供了两个属性。一是 this.element
,
它指向一个只包含一个元素的 jQuery 对象,如果插件是由包含多个元素的 jQuery
对象调用时,会给其中的每一个元素都分配一个插件实例,
并让 this.element
指向它;二是 this.options
,
是包含键值对形式的插件参数的 hash 对象,插件的参数就是像这样传递进来的。
本例中使用了 nmk
作为命名空间。
命名空间 ui
则是保留给官方 jQuery UI 插件的。
创建自己的插件的时候,应该使用自有的命名空间的,
这样可以让人一看就清楚这插件哪来的,是否是一个大体系的一部分。
当我们调用 jQuery.widget
时,与创建标准插件的方式一样,
它也是通过往 jQuery.fn
上面添加方法的方式来扩展 jQuery 对象。
而那个方法的名称就是我们定义的插件名称除去命名空间的部分,案例中是
jQuery.fn.progressbar
。调用时所传递的参数会传递给插件实例的
this.options
。在下面的代码中,我们可以在参数中设置一些默认值。
在设计 API 的时候,你应该先搞清楚最通常的用例,并据此设定相应的默认参数,
那么这些参数就成为可选项了。
Example 8.5. 给 widget 设置默认值
$.widget("nmk.progressbar", { // default options options: { value: 0 }, _create: function() { var progress = this.options.value + "%"; this.element .addClass( "progressbar" ) .text( progress ); } });
接下来就要初始化进度条了。我们使它可以通过调用插件实例方法的方式来执行一些操作。 要给插件定义方法,只需要将其实现代码放在定义体内即可。 我们也可以通过在方法名前加下划线的方式来定义“私有”方法。
Example 8.6. 创建 widget 的方法
$.widget("nmk.progressbar", { options: { value: 0 }, _create: function() { var progress = this.options.value + "%"; this.element .addClass("progressbar") .text(progress); }, // create a public method value: function(value) { // no value passed, act as a getter if (value === undefined) { return this.options.value; // value passed, act as a setter } else { this.options.value = this._constrain(value); var progress = this.options.value + "%"; this.element.text(progress); } }, // create a private method _constrain: function(value) { if (value > 100) { value = 100; } if (value < 0) { value = 0; } return value; } });
将方法名作为参数传进去即可调用插件实例的方法。 如果调用的方法需要传递参数,只需要将那些参数作为后续参数一同传递。
Example 8.7. 调用插件实例的方法
var bar = $("<div></div>") .appendTo("body") .progressbar({ value: 20 }); // get the current value alert(bar.progressbar("value")); // update the value bar.progressbar("value", 50); // get the current value again alert(bar.progressbar("value"));
初始化用所用的 jQuery 方法,向它传递方法名就可以执行方法,这看起来似乎很奇怪。 但这样可以在维持原来的链式调用的方式的同时,防止 jQuery 命名空间被污染。
有一个方法 option
,是自动生成的。它可以实现在初始化过后,
对参数进行查询或设置,就像 css,attr 的用法那样,只传名字时是查询,
名字和值都有时是做设置,如果是包含键值对的 hash 对象则进行多项设置。
进行查询时,插件会返回当前该参数的值。
做设置时,插件的 _setOption
方法会被调用,修改多少个就调用多少次。
我们可以自己实现 _setOption
方法来响应这些参数的修改。
Example 8.8. 当参数被修改时执行一些操作
$.widget("nmk.progressbar", { options: { value: 0 }, _create: function() { this.element.addClass("progressbar"); this._update(); }, _setOption: function(key, value) { this.options[key] = value; this._update(); }, _update: function() { var progress = this.options.value + "%"; this.element.text(progress); } });
扩展插件的一个最简单的办法就是添加回调功能,
这样使用者就可以根据插件状态的改变来采取行动。下面,我们来尝试添加一个回调功能,
在进度达到 100% 时触发。_trigger
方法介绍三个参数:
回调名称,触发回调的本地事件对象以及相关的数据。虽然其中只有回调名称是必须的,
不过其它参数对使用者来说挺有用的。比如说,创建一个可拖拽插件,
我们可以在触发回调时将原生的 mouseover 事件对象传递过去,
用户在回调函数里就可以根据这个对象中的 x/y 坐标对拖拽进行一些处理。
Example 8.9. 提供回调功能让用户进行扩展
$.widget("nmk.progressbar", { options: { value: 0 }, _create: function() { this.element.addClass("progressbar"); this._update(); }, _setOption: function(key, value) { this.options[key] = value; this._update(); }, _update: function() { var progress = this.options.value + "%"; this.element.text(progress); if (this.options.value == 100) { this._trigger("complete", null, { value: 100 }); } } });
回调函数实际上只是另外一种参数,因此你也可以像其它参数一样进行查询和修改了。 无论回调函数是否设置,事件都会触发的。事件类型则是由插件名称和回调名称合并而成。 回调和事件被触发时会收到同样的两个参数:事件对象和相关数据。可以看下面的例子。
如果你的插件提供些功能是允许用户阻止操作的,最好的方式就是提供一个可撤销的回调。
用户可以像撤销原生事件那样,调用 event.preventDefault()
或者 return false
,去撤销回调和相关的事件。如果用户撤销了回调,_trigger
方法会返回 false,
在插件中就可以据此采取相应的动作。
Example 8.10. 绑定 widget 事件
var bar = $("<div></div>") .appendTo("body") .progressbar({ complete: function(event, data) { alert( "Callbacks are great!" ); } }) .bind("progressbarcomplete", function(event, data) { alert("Events bubble and support many handlers for extreme flexibility."); alert("The progress bar value is " + data.value); }); bar.progressbar("option", "value", 100);
有时候,插件让用户可以应用,然后过一阵再解除应用是有意义的。
这可以通过 destroy 方法的来实现。在 destroy
方法内部,
你应该取消你的插件能造成的所有修改,初始化过程中或者后面的使用中造成的。
destroy
方法在 DOM 删除时会被自动调用,所以它可以用于垃圾回收。
默认的 destroy
方法会删掉 DOM 元素与插件实例直接的连接,
所以在覆盖它时是调用原先插件提供的基础 destroy
方法,是很重要的。
Example 8.11. 给 widget 添加 destroy 方法
$.widget( "nmk.progressbar", { options: { value: 0 }, _create: function() { this.element.addClass("progressbar"); this._update(); }, _setOption: function(key, value) { this.options[key] = value; this._update(); }, _update: function() { var progress = this.options.value + "%"; this.element.text(progress); if (this.options.value == 100 ) { this._trigger("complete", null, { value: 100 }); } }, destroy: function() { this.element .removeClass("progressbar") .text(""); // call the base destroy function $.Widget.prototype.destroy.call(this); } });
For this exercise, your task is to identify, download, and implement a table sorting plugin on the index.html page. When you’re done, all columns in the table on the page should be sortable.
Open the file /exercises/index.html
in your
browser. Use the file /exercises/js/stripe.js
. Your
task is to write a plugin called "stripe" that you can call on any table
element. When the plugin is called on a table element, it should change the
color of odd rows in the table body to a user-specified color.
$('#myTable').stripe('#cccccc');
Don't forget to return the table so other methods can be chained after the plugin!
本章涵盖一些 jQuery 和 JavaScript 的最佳实践(排名不分先后), 而其中有许多是基于 Paul Irish 的演讲 jQuery Anti-Patterns for Performance。
在一个 for 循环中,不要每次都访问数组的 length 属性,可以预先缓存起来。
var myLength = myArray.length; for (var i = 0; i < myLength; i++) { // do stuff }
进行 DOM 操作是有代价的,如果需要往 DOM 中添加大量元素, 你应该一次批量完成,而不是一次一个。
// 别这样... $.each(myArray, function(i, item) { var newListItem = '<li>' + item + '</li>'; $('#ballers').append(newListItem); }); // 好的做法是这样的 var frag = document.createDocumentFragment(); $.each(myArray, function(i, item) { var newListItem = '<li>' + item + '</li>'; frag.appendChild(newListItem); }); $('#ballers')[0].appendChild(frag); // 或者这样的 var myHtml = ''; $.each(myArray, function(i, item) { html += '<li>' + item + '</li>'; }); $('#ballers').html(myHtml);
不要做重复的事情,如果你在做重复的工作,意味着出问题了。
// 丑 if ($eventfade.data('currently') != 'showing') { $eventfade.stop(); } if ($eventhover.data('currently') != 'showing') { $eventhover.stop(); } if ($spans.data('currently') != 'showing') { $spans.stop(); } // 漂亮!! var $elems = [$eventfade, $eventhover, $spans]; $.each($elems, function(i,elem) { if (elem.data('currently') != 'showing') { elem.stop(); } });
匿名函数到处乱飞是很痛苦的,它们难以调试,维护,测试以及重用。 要远离痛苦,我们可以使用对象封装,将那些处理和回调函数组织并通过命名管理起来。
// 纠结 $(document).ready(function() { $('#magic').click(function(e) { $('#yayeffects').slideUp(function() { // ... }); }); $('#happiness').load(url + ' #unicorns', function() { // ... }); }); // 清爽 var PI = { onReady : function() { $('#magic').click(PI.candyMtn); $('#happiness').load(PI.url + ' #unicorns', PI.unicornCb); }, candyMtn : function(e) { $('#yayeffects').slideUp(PI.slideCb); }, slideCb : function() { ... }, unicornCb : function() { ... } }; $(document).ready(PI.onReady);
随着越来越多浏览器实现了document.querySelectorAll()
,
选择器的重担从 jQuery 转移了给浏览器,其优化已经没有以前那么重要了。
不过,有些 tips 还是需要紧记的。
选择器以 ID 开始总是最好的。
// 快 $('#container div.robotarm'); // 非常快 $('#container').find('div.robotarm');
使用 $.fn.find
的方式会更快是因为在第一步里只有 ID,
根本没用到快速选择器引擎,而是使用了浏览器内置的极速的
document.getElementById()
。
选择器的右侧部分应该尽可能具体,左侧则不需要那么具体。
// 未优化的 $('div.data .gonzalez'); // 优化过的 $('.data td.gonzalez');
如果可以,尽量在选择器靠右侧的部分使用 tag.class
,
而左侧只有 tag
或者只有 .class
。
避免过度的具体
$('.data table.attendees td.gonzalez'); // 如果可以不写中间的部分会更好 $('.data td.gonzalez');
清爽的 DOM 结构也有助于改善选择器的性能, 选择器引擎可以少跑几层去寻觅那个元素了。
事件委派让你可以给一个容器元素(如一个无序列表)绑定事件处理器,
而不需给容器内每个元素(如列表中的一项)都去绑定。jQuery 提供 $.fn.live 和
$.fn.delegate 使实现起来变得很简单。你应该尽量使用 $.fn.delegate
而不是 $.fn.live
,因为它消除了那些不必要的选择过滤过程,
而且其明确的上下文(相对于 $.fn.live
的上下文是 document
来说的)减少了大约 80% 的开销。
除了有性能提升的好处,事件委派也使你在往容器里添加新元素时无需重新绑定事件, 因为已经有了。
// 如果列表里面元素很多,不堪想象 $('li.trigger').click(handlerFn); // 这样写好一点,用 $.fn.live 做事件委派 $('li.trigger').live('click', handlerFn); // 最好的做法,用 $.fn.delegate 做事件委派,还可以指定一个上下文 $('#myList').delegate('li.trigger', 'click', handlerFn);
DOM 操作是慢的,你应该尽量避免去操作它。jQuery 在 1.4 版引入了 $.fn.detach
来帮助解决这个问题:让你可以先将元素从 DOM 树中剥离出来再进行操作。
var $table = $('#myTable'); var $parent = $table.parent(); $table.detach(); // ... 添加大量的行到表格中去 $parent.append(table);
如果你在用 $.fn.css 给多于 20 个元素修改 CSS,考虑一下添加一个 style 标签, 这样可以速度可以提升 60%。
// 操作大于 20 个元素是不错的,但少于时就不值了 $('a.swedberg').css('color', '#asd123'); $('<style type="text/css">a.swedberg { color : #asd123 }</style>') .appendTo('head');
将 $.data 应用于 DOM 元素比直接调用 jQuery 选择结果的 $.fn.data 要快上 10 倍。 虽然,这要先确定你是理解 DOM 元素与 jQuery 选择结果之间的区别的。
// 常规写法 $(elem).data(key,value); // 快 10 倍 $.data(elem,key,value);
jQuery 不会告诉你,你正在对空白的选择结果执行一段代码,而且会像一点错都没有那样执行了。 是否要核实选择结果是否为空,完全由你决定的。
// 太遭了,执行了三个方法后才意识到里面是空的 $('#nosuchthing').slideUp(); // 这会好点,就是代码有点长 var $mySelection = $('#nosuchthing'); if ($mySelection.length) { mySelection.slideUp(); } // 最好的做法是添加一个 doOnce 插件 jQuery.fn.doOnce = function(func){ this.length && func.apply(this); return this; } $('li.cartitems').doOnce(function(){ // make it ajax! \o/ });
这层保护是适用于 jQuery UI widget,因为即使操作的元素为空其开销也不少。
变量可以定义在一个 statement 内,用不着多个。
// 老套的写法 var test = 1; var test2 = function() { ... }; var test3 = test2(test); // 新鲜的写法 var test = 1, test2 = function() { ... }, test3 = test2(test);
在自执行的函数中,变量定义可以完全省掉。
(function(foo, bar) { ... })(1, 2);
// 土办法 if (type == 'foo' || type == 'bar') { ... } // 先进经验是 if (/^(foo|bar)$/.test(type)) { ... } // 应用对象属性查找 if (({ foo : 1, bar : 1 })[type]) { ... }
将 jQuery 的源文件当成自己的文档 - 书签 http://bit.ly/jqsource 并且多看多思考。
当你不仅想通过jQuery向你的网站添加简单功能,还打算开发成熟的客户端应用程序时,你必须要考虑如何组织你的代码。这一章节中,我们将关注你在jQuery应用程序中可以使用的多种代码组织模式,研究RequireJS依赖管理和系统构建。
在我们进入代码组织模式之前,有必要了解一些公认的良好代码组织模式的概念。
你的代码应该分割成功能单元——模块,服务等等。避免将所有代码放进一个庞大的$(document).ready()
块中。不严格地讲,这就是所谓的封装。
避免重复。识别功能块中的相似性,使用继承方式避免出现重复性代码。
尽管jQuery的特点是以DOM为中心,但JavaScript应用程序中DOM并不是全部。记住,并非所有的功能块都需要或应该拥有一个DOM表示。
功能单元应该松藕合——一个功能单元应当以其特有的方式存在,功能单元之间的通信应当通过定制事件或pub/sub这样的消息系统进行处理。尽可能避免功能单元之间的直接通信。
松藕合的概念对于首次挑战复杂应用程序的开发人员可能尤为烦恼,因此当你正在入门时要格外关注。
代码组织的第一步是将应用程序分离成不同的代码块;有时,哪怕只做到这一点也是很有意义的。
对象原语可能是最简单的关联代码封装方式了。它不会为属性或方法提供任何隐私保护,但它对于从你的代码中消除匿名函数,集中配置选项,简化重用和重构都很有帮助。
Example 10.1. 对象原语
var myFeature = { myProperty : 'hello', myMethod : function() { console.log(myFeature.myProperty); }, init : function(settings) { myFeature.settings = settings; }, readSettings : function() { console.log(myFeature.settings); } }; myFeature.myProperty; // 'hello' myFeature.myMethod(); // 记录 'hello' myFeature.init({ foo : 'bar' }); myFeature.readSettings(); // 记录 { foo : 'bar' }
上面的对象原语简单地将一个对象分配给了一个变量。该对象具有一个属性和几个方法。所有的属性和方法都是公有的,因此你应用程序的任何部分都可以访问对象的属性、调用对象的方法。尽管有一个init方法,但在对象实例化之前调用它却没有任何必要。
我们怎样将这种模式应用到jQuery代码中?让我们通过编写传统jQuery风格的代码来说明吧:
//点击一个列表项加载一些内容 //引用列表项的ID并隐藏兄弟列表项的内容 $(document).ready(function() { $('#myFeature li') .append('<div/>') .click(function() { var $this = $(this); var $div = $this.find('div'); $div.load('foo.php?item=' + $this.attr('id'), function() { $div.show(); $this.siblings() .find('div').hide(); } ); }); });
如果它出现在我们的应用程序范围内,抛弃它可能会更好。换句话说,如果它是一个大型应用程序的一部分,我们最好将这个功能从孤立的功能中分离出来。我们可能想把代码中的URL移到一个配置区域。最后,我们可能想摆脱链式代码的束缚让以后修改功能块更简单一点。
Example 10.2. 将对象原语用到jQuery特性中
var myFeature = { init : function(settings) { myFeature.config = { $items : $('#myFeature li'), $container : $('<div class="container"></div>'), urlBase : '/foo.php?item=' }; // 允许重载默认配置 $.extend(myFeature.config, settings); myFeature.setup(); }, setup : function() { myFeature.config.$items .each(myFeature.createContainer) .click(myFeature.showItem); }, createContainer : function() { var $i = $(this), $c = myFeature.config.$container.clone() .appendTo($i); $i.data('container', $c); }, buildUrl : function() { return myFeature.config.urlBase + myFeature.$currentItem.attr('id'); }, showItem : function() { var myFeature.$currentItem = $(this); myFeature.getContent(myFeature.showContent); }, getContent : function(callback) { var url = myFeature.buildUrl(); myFeature.$currentItem .data('container').load(url, callback); }, showContent : function() { myFeature.$currentItem .data('container').show(); myFeature.hideContent(); }, hideContent : function() { myFeature.$currentItem.siblings() .each(function() { $(this).data('container').hide(); }); } }; $(document).ready(myFeature.init);
你会关心的第一件事就是这种方式比原来的代码明显要长得多——此外,如果它在我们应用程序的范围内,对象原语的使用则有点泛滥了。假设它不在我们程序的范围内,我们便发现了这样几个事实: The first thing you'll notice is that this approach is obviously far longer than the original — again, if this were the extent of our application, using an object literal would likely be overkill. Assuming it's not the extent of our application, though, we've gained several things:
我们已经将功能特性分解成了短小的方法。如果以后我们需要改变显示内容,在哪里更改将一目了然。
我们已经消除了匿名函数的使用。
我们已经将配置选项移出了代码本身,并将这些配置选项放到了核心位置。
我们已经摆脱了链式代码的束缚,使代码易于重构,重新合成,重新组织。
作为不一般的特性,对象原语是对在$(document).ready()块中在编写冗长代码的明显改进,它迫使我们考虑应用程序的功能块。无论如何,和在$(document).ready()块中简单定义大量函数相比,它们也好不到哪里去。
组件模式突破了对象原语的一些局限性,如有必要可以对外暴露出公有API,为变量和函数提供隐私保护。
Example 10.3. 组件模式
var feature =(function() { //私有变量和函数 var privateThing = 'secret', publicThing = 'not secret', changePrivateThing = function() { privateThing = 'super secret'; }, sayPrivateThing = function() { console.log(privateThing); changePrivateThing(); }; //公有API return { publicThing : publicThing, sayPrivateThing : sayPrivateThing } })(); feature.publicThing; // 'not secret' feature.sayPrivateThing(); //记录'secret'并且改变privateThing的值
在上面的例子里,我们自动执行了一个返回对象的匿名函数。在该函数内部,我们定义了一些变量,因为这些变量是定义在函数内部的,我们无法在函数之外去访问它们,除非我们把它放到返回的对象中。这意味着函数外部的代码无法访问privateThing
变量或changePrivateThing
函数。不管怎样,sayPrivateThing
却可以访问privateThing
和changePrivateThing
,因为它们和sayPrivateThing
处于相同的作用域。
这种模式之所以十分强大,是因为当需要暴露由返回对象的属性和方法构成的受限API时,你可以隐藏这些变量名,形成私有的变量和函数。
下面是上一个例子的修正版本,它向我们展示了仅仅暴露该组件一个公有方法showItemByIndex()
时,通过组件模式创建相同的特性。
Example 10.4. 通过jQuery使用组件模式
$(document).ready(function() { var feature = (function() { var $items = $('#myFeature li'), $container = $('<div class="container"></div>'), $currentItem, urlBase = '/foo.php?item=', createContainer = function() { var $i = $(this), $c = $container.clone().appendTo($i); $i.data('container', $c); }, buildUrl = function() { return urlBase + $currentItem.attr('id'); }, showItem = function() { var $currentItem = $(this); getContent(showContent); }, showItemByIndex = function(idx) { $.proxy(showItem, $items.get(idx)); }, getContent = function(callback) { $currentItem.data('container').load(buildUrl(), callback); }, showContent = function() { $currentItem.data('container').show(); hideContent(); }, hideContent = function() { $currentItem.siblings() .each(function() { $(this).data('container').hide(); }); }; $items .each(createContainer) .click(showItem); return { showItemByIndex : showItemByIndex }; })(); feature.showItemByIndex(0); });
这一章节大量基于优秀的RequireJS文档http://requirejs.org/docs/jquery.html,并且得到RequireJS作者James Burke的许可。
当项目达到一定规模时,管理项目的脚本模块开始变得非常棘手。你显然需要按正确的次序组织好这些脚本,并且开始认真考虑将脚本打包部署,只让一个或少数请求去载入这些脚本。你或许还想在页面加载完成后立即加载这些脚本代码。 When a project reaches a certain size, managing the script modules for a project starts to get tricky. You need to be sure to sequence the scripts in the right order, and you need to start seriously thinking about combining scripts together into a bundle for deployment, so that only one or a very small number of requests are made to load the scripts. You may also want to load code on the fly, after page load.
RequireJS是James Burke设计的一个依赖管理工具,它可以帮助你管理脚本模块并以正确的次序加载脚本,不需要改变标记即可以通过RequireJS优化工具轻松组合后续的脚本。它提供一种简单的方式在页面加载完成后载入脚本,随着时间的推移可以有效扩充脚本文件的尺寸。
RequireJS的模块系统可以帮助你定义作用域良好的模块,即使你却不关注这套系统也能获得依赖管理和构建时优化的好处。如果你开始创建模块化的代码并在少数场合重用它们,久而久之,RequireJS的模块格式会使编写联机加载的封装代码十分简单。特别是你想使用国际化字符串,让你的项目针对不同语言进行本地化,或者加载HTML字符串并确保在执行代码前这些字符串确实有效,或者只是把JSONP服务当作依赖关系来使用,RequireJS都是你的得力伙伴。
在jQuery中使用RequireJS最简单的方式是下载一个内建RequireJS的jQuery构建版本。这个构建版本不包括RequireJS和jQuery功能发生重复的部分。你还会发现下载使用RequireJS的jQuery示例项目对你也很有帮助。
在页面中使用RequireJS相当简单:只需要引入内建RequireJS的jQuery和你的应用程序文件。下面的示例假设jQuery和你的其它脚本都位于scripts/
目录。
Example 10.5. 使用RequireJS:一个简单示例
<!DOCTYPE html> <html> <head> <title>jQuery+RequireJS Sample Page</title> <script src="scripts/require-jquery.js"></script> <script>require(["app"]);</script> </head> <body> <h1>jQuery+RequireJS Sample Page</h1> </body> </html>
调用require(["app"])
是在告诉RequireJS加载scripts/app.js
文件。RequireJS将从require-jquery.js
所在的目录加载所有传入require()
的依赖脚本,且不需要.js
扩展名,不过也可以将它配置成另一种完全不同的工作方式。如果你乐意使用完整路径,还可以通过下面的方式来处理:
<script>require(["scripts/app.js"]);</script>
app.js
里有什么?有另一种方式可以调用require.js
来加载你需要的脚本,在页面中随心所欲的进行初始化工作。这个示例中app.js
脚本加载了两个插件:jquery.alpha.js
和jquery.beta.js
(不是什么真正的插件,只是随便举个例子)。这些插件需要放进和require-jquery.js
相同的目录:
Example 10.6. 使用依赖的简单JavaScript文件
require(["jquery.alpha", "jquery.beta"], function() { //jquery.alpha.js和jquery.beta.js插件已经加载。 $(function() { $('body').alpha().beta(); }); });
通过require.def()
,RequireJS让定义可重用的模块变得十分简单。RequireJS模块可以使用依赖来定义一个模块,并且Require模块可以返回一个值——不管是一个对象还是一个函数——并让其它模块来使用。
如果你的模块没有任何依赖性,只需要指定一个模块名称作为第一个参数调用require.def()
。第二个参数只是一个定义了模块属性的对象原语。例如:
Example 10.7. 定义一个没有依赖性的RequireJS模块
require.def("my/simpleshirt", { color: "black", size: "unisize" } );
这个示例将被放到my/simpleshirt.js文件中。
如果你的模块存在依赖性,你可以将依赖关系作为第二个参数传递给require.def()
(用数组的方式),接着传递一个函数作为第三个参数。这个函数将在所有依赖关系加载完成后被调用,以便定义这个模块。
Example 10.8. 定义一个有依赖性的RequireJS模块
require.def("my/shirt", ["my/cart", "my/inventory"], function(cart, inventory) { //返回一个对象以便定义"my/shirt"模块。 return { color: "blue", size: "large" addToCart: function() { inventory.decrement(this); cart.add(this); } } } );
在这个例子里创建了my/shirt模块,它依赖于my/cart和my/inventory。在磁盘上,这些文件被构造成这样:
my/cart.js my/inventory.js my/shirt.js
定义my/shirt
的函数在my/cart
和my/inventory
加载前不会被调用,函数以cart
和inventory
参数接收模块。函数参数的顺序必须和依赖关系数组中的依赖关系次序一致。调用函数后返回的对象定义了my/shirt
模块。通过这种方式定义模块,my/shirt
不会作为一个全局对象存在。在模块中定义全局变量被明确地阻止,所以同一时间可以在同一个页面使用多个不同版本的模块。
模块中返回对象不是必需的;允许从函数中返回任何有效值。
Example 10.9. 定义一个返回函数的RequireJS模块
require.def("my/title", ["my/dependency1", "my/dependency2"], function(dep1, dep2) { //返回一个函数以定义"my/title",该函数可返回或设置窗口标题。 return function(title) { return title ? (window.title = title) : window.title; } } );
每个JavaScript文件必须只有唯一的一个模块。
一旦你结合RequireJS进行依赖管理,实施页面优化将会非常简单。下载RequireJS源文件并放到你喜欢的任何地方,最好是放到web开发以外的区域。因为这个例子的原因,RequireJS源文件被放到了webapp
的同一级目录,它包括了HTML页面和包含所有脚本的目录。完整的目录结构如下:
requirejs/ (用于构建工具) webapp/app.html webapp/scripts/app.js webapp/scripts/require-jquery.js webapp/scripts/jquery.alpha.js webapp/scripts/jquery.beta.js
接着,在这个拥有require-jquery.js
和app.js的脚本目录里,创建叫作app.build.js的文件,并且具有下列内容:
Example 10.10. RequireJS构建配置文件
{ appDir: "../", baseUrl: "scripts/", dir: "../../webapp-build", //如果你想通过闭包编译器用"simple"优化模式压缩代码, //可以注释掉optimize这一行 optimize: "none", modules: [ { name: "app" } ] }
要使用这个构建工具,你需要安装Java 6。闭包编译器用于JavaScript压缩环节(如果optimize:
"none"
被注释掉),它需要Java 6的支持。
要开始进行构建工作,需进入webapp/scripts目录,执行下列命令:
# 非Windows系统 ../../requirejs/build/build.sh app.build.js # Windows系统 ..\..\requirejs\build\build.bat app.build.js
现在,webapp-build目录中将会生成app.js
,jquery.alpha.js
和jquery.beta.js
这样几个文件。如果在浏览器中打开webapp-build
目录中的app.html
文件,你不会看到任何加载jquery.alpha.js
和jquery.beta.js
的网络请求。
在你的浏览器中打开/exercises/portlets.html
文件,使用/exercises/js/portlets.js
文件,你的任务是使用模块模式编写一个portlet创建函数,就像下列代码那样工作:
var myPortlet = Portlet({ title : 'Curry', source : 'data/html/curry.html', initialState : 'open' // 或'closed' }); myPortlet.$element.appendTo('body');
每个Portlet应该是一个DIV,带有一个标题、一个内容区域、一个打开/关闭按钮、一个删除portlet的按钮和一个刷新portlet的按钮。Portlet函数返回的portlet应该具有下列公有API:
myPortlet.open(); // 强制打开状态 myPortlet.close(); // 强制关闭状态 myPortlet.toggle(); // 切换开/关状态 myPortlet.refresh(); // 刷新内容 myPortlet.destroy(); // 从页面中移除一个portlet myPortlet.setSource('data/html/onions.html'); // 改变数据源
We’re all familiar with the basic events — click, mouseover, focus, blur, submit, etc. — that we can latch on to as a user interacts with the browser. Custom events open up a whole new world of event-driven programming. In this chapter, we’ll use jQuery’s custom events system to make a simple Twitter search application.
It can be difficult at first to understand why you'd want to use custom events, when the built-in events seem to suit your needs just fine. It turns out that custom events offer a whole new way of thinking about event-driven JavaScript. Instead of focusing on the element that triggers an action, custom events put the spotlight on the element being acted upon. This brings a bevy of benefits, including:
Behaviors of the target element can easily be triggered by different elements using the same code.
Behaviors can be triggered across multiple, similar, target elements at once.
Behaviors are more clearly associated with the target element in code, making code easier to read and maintain.
Why should you care? An example is probably the best way to explain. Suppose you have a lightbulb in a room in a house. The lightbulb is currently turned on, and it’s controlled by two three-way switches and a clapper:
<div class="room" id="kitchen"> <div class="lightbulb on"></div> <div class="switch"></div> <div class="switch"></div> <div class="clapper"></div> </div>
Triggering the clapper or either of the switches will change the state of the lightbulb. The switches and the clapper don’t care what state the lightbulb is in; they just want to change the state.
Without custom events, you might write some code like this:
$('.switch, .clapper').click(function() { var $light = $(this).parent().find('.lightbulb'); if ($light.hasClass('on')) { $light.removeClass('on').addClass('off'); } else { $light.removeClass('off').addClass('on'); } });
With custom events, your code might look more like this:
$('.lightbulb').bind('changeState', function(e) { var $light = $(this); if ($light.hasClass('on')) { $light.removeClass('on').addClass('off'); } else { $light.removeClass('off').addClass('on'); } }); $('.switch, .clapper').click(function() { $(this).parent().find('.lightbulb').trigger('changeState'); });
This last bit of code is not that exciting, but something important has happened: we’ve moved the behavior of the lightbulb to the lightbulb, and away from the switches and the clapper.
Let’s make our example a little more interesting. We’ll add another room to our house, along with a master switch, as shown here:
<div class="room" id="kitchen"> <div class="lightbulb on"></div> <div class="switch"></div> <div class="switch"></div> <div class="clapper"></div> </div> <div class="room" id="bedroom"> <div class="lightbulb on"></div> <div class="switch"></div> <div class="switch"></div> <div class="clapper"></div> </div> <div id="master_switch"></div>
If there are any lights on in the house, we want the master switch
to turn all the lights off; otherwise, we want it to turn all lights on.
To accomplish this, we’ll add two more custom events to the lightbulbs:
turnOn
and turnOff
. We’ll make use of them in
the changeState
custom event, and use some logic to decide
which one the master switch should trigger:
$('.lightbulb') .bind('changeState', function(e) { var $light = $(this); if ($light.hasClass('on')) { $light.trigger('turnOff'); } else { $light.trigger('turnOn'); } }) .bind('turnOn', function(e) { $(this).removeClass('off').addClass('on'); }) .bind('turnOff', function(e) { $(this).removeClass('off').addClass('on'); }); $('.switch, .clapper').click(function() { $(this).parent().find('.lightbulb').trigger('changeState'); }); $('#master_switch').click(function() { if ($('.lightbulb.on').length) { $('.lightbulb').trigger('turnOff'); } else { $('.lightbulb').trigger('turnOn'); } });
Note how the behavior of the master switch is attached to the master switch; the behavior of a lightbulb belongs to the lightbulbs.
If you’re accustomed to object-oriented programming, you may find
it useful to think of custom events as methods of objects. Loosely
speaking, the object to which the method belongs is created via the
jQuery selector. Binding the changeState custom event to all
$(‘.light’)
elements is akin to having a class called
Light
with a method of changeState
, and then
instantiating new Light
objects for each element with a
classname of light.
To demonstrate the power of custom events, we’re going to create a simple tool for searching Twitter. The tool will offer several ways for a user to add search terms to the display: by entering a search term in a text box, by entering multiple search terms in the URL, and by querying Twitter for trending terms.
The results for each term will be shown in a results container; these containers will be able to be expanded, collapsed, refreshed, and removed, either individually or all at once.
When we’re done, it will look like this:
We’ll start with some basic HTML:
<h1>Twitter Search</h1> <input type="button" id="get_trends" value="Load Trending Terms" /> <form> <input type="text" class="input_text" id="search_term" /> <input type="submit" class="input_submit" value="Add Search Term" /> </form> <div id="twitter"> <div class="template results"> <h2>Search Results for <span class="search_term"></span></h2> </div> </div>
This gives us a container (#twitter) for our widget, a template for our results containers (hidden via CSS), and a simple form where users can input a search term. (For the sake of simplicity, we’re going to assume that our application is JavaScript-only and that our users will always have CSS.)
There are two types of objects we’ll want to act on: the results containers, and the Twitter container.
The results containers are the heart of the application. We’ll create a plugin that will prepare each results container once it’s added to the Twitter container. Among other things, it will bind the custom events for each container and add the action buttons at the top right of each container. Each results container will have the following custom events:
Mark the container as being in the “refreshing” state, and fire the request to fetch the data for the search term.
Receive the returned JSON data and use it to populate the container.
Remove the container from the page after the user verifies the request to do so. Verification can be bypassed by passing true as the second argument to the event handler. The remove event also removes the term associated with the results container from the global object containing the search terms.
Add a class of collapsed to the container, which will hide the results via CSS. It will also turn the container’s “Collapse” button into an “Expand” button.
Remove the collapsed class from the container. It will also turn the container’s “Expand” button into a “Collapse” button.
The plugin is also responsible for adding the action buttons to the container. It binds a click event to each action’s list item, and uses the list item’s class to determine which custom event will be triggered on the corresponding results container.
$.fn.twitterResult = function(settings) { return this.each(function() { var $results = $(this), $actions = $.fn.twitterResult.actions = $.fn.twitterResult.actions || $.fn.twitterResult.createActions(), $a = $actions.clone().prependTo($results), term = settings.term; $results.find('span.search_term').text(term); $.each( ['refresh', 'populate', 'remove', 'collapse', 'expand'], function(i, ev) { $results.bind( ev, { term : term }, $.fn.twitterResult.events[ev] ); } ); // use the class of each action to figure out // which event it will trigger on the results panel $a.find('li').click(function() { // pass the li that was clicked to the function // so it can be manipulated if needed $results.trigger($(this).attr('class'), [ $(this) ]); }); }); }; $.fn.twitterResult.createActions = function() { return $('<ul class="actions" />').append( '<li class="refresh">Refresh</li>' + '<li class="remove">Remove</li>' + '<li class="collapse">Collapse</li>' ); }; $.fn.twitterResult.events = { refresh : function(e) { // indicate that the results are refreshing var $this = $(this).addClass('refreshing'); $this.find('p.tweet').remove(); $results.append('<p class="loading">Loading ...</p>'); // get the twitter data using jsonp $.getJSON( 'http://search.twitter.com/search.json?q=' + escape(e.data.term) + '&rpp=5&callback=?', function(json) { $this.trigger('populate', [ json ]); } ); }, populate : function(e, json) { var results = json.results; var $this = $(this); $this.find('p.loading').remove(); $.each(results, function(i,result) { var tweet = '<p class="tweet">' + '<a href="http://twitter.com/' + result.from_user + '">' + result.from_user + '</a>: ' + result.text + ' <span class="date">' + result.created_at + '</span>' + '</p>'; $this.append(tweet); }); // indicate that the results // are done refreshing $this.removeClass('refreshing'); }, remove : function(e, force) { if ( !force && !confirm('Remove panel for term ' + e.data.term + '?') ) { return; } $(this).remove(); // indicate that we no longer // have a panel for the term search_terms[e.data.term] = 0; }, collapse : function(e) { $(this).find('li.collapse').removeClass('collapse') .addClass('expand').text('Expand'); $(this).addClass('collapsed'); }, expand : function(e) { $(this).find('li.expand').removeClass('expand') .addClass('collapse').text('Collapse'); $(this).removeClass('collapsed'); } };
The Twitter container itself will have just two custom events:
Receives a search term and checks to determine whether
there’s already a results container for the term; if not, adds a
results container using the results template, set up the results
container using the $.fn.twitterResult
plugin
discussed above, and then triggers the refresh
event on the results container in order to actually load the
results. Finally, it will store the search term so the
application knows not to re-fetch the term.
Queries Twitter for the top 10 trending terms, then
iterates over them and triggers the getResults
event for each of them, thereby adding a results container for
each term.
Here's how the Twitter container bindings look:
$('#twitter') .bind('getResults', function(e, term) { // make sure we don't have a box for this term already if (!search_terms[term]) { var $this = $(this); var $template = $this.find('div.template'); // make a copy of the template div // and insert it as the first results box $results = $template.clone(). removeClass('template'). insertBefore($this.find('div:first')). twitterResult({ 'term' : term }); // load the content using the "refresh" // custom event that we bound to the results container $results.trigger('refresh'); search_terms[term] = 1; } }) .bind('getTrends', function(e) { var $this = $(this); $.getJSON('http://search.twitter.com/trends.json?callback=?', function(json) { var trends = json.trends; $.each(trends, function(i, trend) { $this.trigger('getResults', [ trend.name ]); }); }); });
So far, we’ve written a lot of code that does approximately nothing, but that’s OK. By specifying all the behaviors that we want our core objects to have, we’ve created a solid framework for rapidly building out the interface.
Let’s start by hooking up our text input and the “Load Trending
Terms” button. For the text input, we’ll capture the term that was
entered in the input and pass it as we trigger the Twitter container’s
getResults
event. Clicking the “Load Trending Terms” will
trigger the Twitter container’s getTrends
event:
$('form').submit(function(e) { e.preventDefault(); var term = $('#search_term').val(); $('#twitter').trigger('getResults', [ term ]); }); $('#get_trends').click(function() { $('#twitter').trigger('getTrends'); });
By adding a few buttons with the appropriate IDs, we can make it possible to remove, collapse, expand, and refresh all results containers at once, as shown below. For the remove button, note how we’re passing a value of true to the event handler as its second argument, telling the event handler that we don’t want to verify the removal of individual containers.
$.each(['refresh', 'expand', 'collapse'], function(i, ev) { $('#' + ev).click(function(e) { $('#twitter div.results').trigger(ev); }); }); $('#remove').click(function(e) { if (confirm('Remove all results?')) { $('#twitter div.results').trigger('remove', [ true ]); } });
Custom events offer a new way of thinking about your code: they put the emphasis on the target of a behavior, not on the element that triggers it. If you take the time at the outset to spell out the pieces of your application, as well as the behaviors those pieces need to exhibit, custom events can provide a powerful way for you to “talk” to those pieces, either one at a time or en masse. Once the behaviors of a piece have been described, it becomes trivial to trigger those behaviors from anywhere, allowing for rapid creation of and experimentation with interface options. Finally, custom events can enhance code readability and maintainability, by making clear the relationship between an element and its behaviors.
You can see the full application at
demos/custom-events.html
and
demos/js/custom-events.js
in the sample
code.
Copyright Rebecca Murphey, released under the Creative Commons Attribution-Share Alike 3.0 United States license.