本文翻译自此文章
你有没有遇到过类似$(".cta").click(function(){})这样的JavaScript代码并且在想“$(‘#x‘)是什么”?如果这些对你想天书一样,请往下读。如果你认为这些代码不可能是真的,请浏览一些jQuery例子,他们都是这种结构。
这篇文章覆盖了像下面一样吓人的代码片段中涉及的关键概念。我们以一个长例子开始,这个长例子是基于一个让一个正方形运动的简单例子(a simple example of animating a square)。你可能不需要每天做这些,但是这是一个简洁整齐的示范:
$(document).ready(function(){ $("button").click(function(){ $("div").animate({height:"toggle"}).append("hi"); }); });
我们将会涉及到上述例子所用的所有知识点和功能,一窥JavaScript函数,jQuery对象以及事件驱动编程的细节。到了最后,你在面对这种神秘代码时将不会感到困惑。
$什么?
第一眼看,$像是一个特殊的复杂的JavaScript功能。但他不是。在JavaScript中, 美元符号没有特别的含义。事实上,$只是一个函数,他有一个别名jQuery函数。
jQuery函数是jQuery库存在的理由(raison d’être of jQuery library)。jQuery是一个消除了不同浏览器之间的JavaScript实现差异的JavaScript库,他还为网页提供了丰富的控制和动画功能。你可以通过引用一份这个库的拷贝来包含jQuery函数(例如,$):
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
或者,你可以下载从官网上一份副本。
jQuery函数通常使用单个参数,不是选择器(selector)就是在页面上的一个JavaScript引用,例如document。选择器是CSS的一部分,在{...}之前。因此,$("div")类似于jQuery("div")。通过选择当前页面上所有的div元素,它的行为十分类似于下面的CSS:
<style> div {…} </style>
在我们开始的例子中,$(document)将document这个JavaScript变量传给了jQuery函数。document变量是浏览器自动设置的,它指的是DOM的最顶端。DOM是浏览器自己对页面中的HTML分析后生成的结果,也是jQuery功能的建立基础。例如,jQuery的$("div")十分类似于document.getElementByTagName("div")。
关键点
要记住,$只是一个函数,他有一个易用的别名叫jQuery函数。
点操作符
在$(document)后面的.操作符显示了丰富的功能。它和JavaScript对象一起使用。简单的说,JavaScript 是一系列属性的集合。例如:
var digger = new Object(); digger.species = "gerbil"; digger.name = "Digger"; digger.color = "white";
在这个例子中,digger是一个JavaScript对象,我们已经赋给了他三个变量:species、name和color。在面向对象的术语中,他们被叫做成员变量。可以用更简单的方式写:
var digger = {species:"gerbil", name:"Digger", color:"white"};
你也可以赋给对象一个函数作为属性。例如:
function meepMeep(){ alert("meep meep"); }
在JavaScript中,变量、函数和对象之间的界限很模糊。所以,一个函数可以很容易的赋值给一个(成员)变量。
digger.speak = meepMeep;
你可以这样调用函数:
digger.speak();
在面向对象中,这叫做成员函数或者是方法。方法调用同一个对象中的其它方法和成员变量。例如:
function myNameIs(){ alert("Meep! I am a " + this.species); } //assign the function digger.sayMyName = myNameIs; //call the function digger.sayMyName();
在myNameIs函数中,特别的变量this指向包含它的对象,this.species等同于digger.species,他们的值是gerbil。如果你试图不通过对象只调用myNameIs函数,this将指向window对象,而this.species将会是window.species,也就是undefined。页面会显示”Meep!I am a undefined.“
对象也可以作为函数的返回值。例如:
function giveMeTheGerbil(){ return digger; }
他将返回一个(全局)变量或对象digger,就像原来的digger一样。
var digger2 = giveMeTheGerbil(); //alerts "Meep! I am a gerbil" digger2.sayMyName();
然而,你可以不使用中间变量而直接用giveMeTheGerbil的返回值调用sayMyName:
giveMeTheGerbil().sayMyName();
仔细观察这个代码,它的编程结构和我们最初的例子里的第一行一样:
$(document).ready(…);
下一节将阐述ready函数做什么。
关键点
对象可以简写成{name:"Digger", species:"gerbil"}。The keyword
this
is used in a function attached to an object (a method) and refers to the containing object.(这句不知该如何翻译)
匿名函数
在JavaScript中有几中创建函数的方法。下满是一种经典的方式(声明式函数),有过编程经验的应该很熟悉:
function meepMeep(){ alert("meep meep"); }
在之前我们已经看到函数可以赋值给变量。我们创建了meepMeep函数并把它赋给了digger.speak。事实上,函数可以被匿名的创建(叫做函数表达式),他没有任何名字并立即赋给一个变量:
var meepMeep = function(){ alert("meep meep"); };
在JavaScript中,函数可以被赋值给变量并像其他变量一样作为实参被传给函数:
function runMe(f){ f(); }
它有一个参数,叫做f。runMe把它当作一个函数并运行他。你可以这样:
runMe(meepMeep);
它将运行meepMeep函数。更有趣的是,你甚至可以不用正式的名字meepMeep。你可以简单的创建他,并立即传递给runMe:
runMe(function(){ alert("meep meep"); });
事实上,任何meepMeep出现的地方,都可以使用等价的匿名函数:
meepMeep();
作为替代,你可以在meepMeep的地方放置一个匿名函数:
(function(){ alert("meep meep"); })();
在JavaScript中,这个技术常用来提供变量作用域。你能明白下面代码要输出什么吗:
var x=3; (function(){ var x=4; console.log("x is " + x); })(); console.log ("x is " + x);
在此处,函数中的var关键字很重要。它在函数内声明了一个变量。在此处,匿名函数声明了一个变量x并赋值为4,然后输出它。因为var关键字,函数的x跟之前一行的var x = 3完全的分开了。代码将会先输出x=4然后是x=3。
这里的console.log存在于现代浏览器(换句话,老版IE不支持),他在浏览器的JavaScript控制台输出相关信息。
匿名函数是下一个难题。jQuery的ready函数就像上面runMe函数的有时间延迟的版本。ready函数等待所有的DOM加载完后才执行提供给它的函数。因此,当document最终ready,下面的匿名函数将会执行:
function(){ $("button").click (…) }
对于程序员来说,要执行那些只能在HTML文档被处理完之后的JavaScript,$(document).ready(...)是一个常用的方法。
关键点
匿名函数是那些没有名字的函数。他们可以赋值给变量,传递给其他函数或者作为立即执行函数来提供作用域。
方法链在更深入例子代码之前,我们需要在复习一次经常发生在JavaScript中的概念。方法链(method chaining)指的是在一行运行多个函数。让我们扩展一下上面giveMeTheGerbil()这个例子:
giveMeTheGerbil().sayMyName();
让我们重新定义一下gerbil相关的函数,让他们返回一个指向自己的引用:
digger.speak = function(){ alert("meep meep"); return this; } digger.sayMyName = function(){ alert("Meep! I am a " + this.species); return this; }
这两个函数对digger做了一些事之后返回digger。这些改变让我们可以链式调用这些函数:
giveMeTheGerbil().speak().sayMyName().speak();
这行代码先运行giveMeTheGerbil,返回一个digger对象引用,现在,这行代码等价于:
digger.speak().sayMyName().speak();
接下来是speak函数,它也返回一个digger对象并在此调用sayMyName函数,一直这样直到最后一个函数。
这样的链式调用在JavaScript非常常见。你可能会见到string对象也有这样的情况。
var s = "I have a dagger."; console.log(s.substring(9, 15).replace("a", "i").toUpperCase());
上面的代码以字符串s开始,提取子串,用i替换a,是所有字符大写并返回一个在控制台日志显示的字符串。
当然,jQuery中也充斥这链式调用,它也出现在我们的例子里:
$("div").animate({height:"toggle"}).append("hi");
代码首先通过$("div")查询页面中全部的<div>并作为jQuery对象的一部分返回他们。然后,它对jQuery对象运行animate方法接着再运行append方法,每一次返回并操作的都是jQuery对象。
这样的调用链可以非常长,这里有一个特别长的调用链。
通常来说,长的调用链会很难调试和维护。避免长调用链始终是个好主意,但在小规模的情况下将非常有用。
关键点
那些返回自身对象引用的方法可以链接在一起,这样就可以在执行长段代码是不用储存中间变量。
jQuery对象
我们例子中使用了几个jQuery方法:ready、click、animate和append。他们都是jQuery对象相关的函数,类似于speak和myNameIs是与digger对象相关的函数,和substr、replace和toUpperCase与string关系一致。
这些函数都是jQuery对象的方法,他们都返回jQuery对象。jQuery对象比digger和string对象都要复杂的多。
之前提过,在JavaScript中,概念之间的界限相当模糊。jQuery对象的行为类似于一个对象和一个数组。你在链式调用时对待他像一个对象,但你也可以对待它象一个数组:
var mydivs = $("div"); for (var i = 0; i < mydivs.length; i++) {console.log(mydivs[i].innerHTML);}
在这个例子中,$("div")查找页面中所有的<div>元素并把生成的jQuery对象存储在mydivs变量中。代码遍历jQuery对象就好像他是DOM中的节点(nodes)数组(实际上,是一个NodeList)。这些节点同样也是包含着各自属性的对象,例如outerHTML和innerHTML。
我们也可以通过把这些节点返回个jQuery对象然后调用jQuery方法html()达到同样的结果。为了实现它,我们把节点传递给$,$可以将任何东西很好的转换为jQuery对象:
var mydivs = $("div"); for (var i = 0; i < mydivs.length; i++) {console.log($(mydivs[i]).html());}
上面两个都可以输出页面中的每个<div>的HTML内容。
记住,当你运行像是$("div").animate(...).append(...)这样的jQuery代码时,动画(animation)将会在jQuery对象中所有的<div>元素中发生,而且他们会作为jQuery对象的一部分传递给下一个在调用链的函数。(对于大多数jQuery函数是这样的,参见这里)
关键点
jQuery函数$和许多jQuery方法像click和animate返回一个jQuery对象,jQuery一半类似于对象,一半类似于数组。类型与数组的部分包含着指向在DOM中的节点。
把所有合在一起
现在,我们可以将例子作为一个整体来看了。$(document)返回一个指向页面自己的jQuery对象。ready方法将会在页面完成解析和DOM可用时执行传递给他的方法:
function(){ $("button").click(…); }
这个函数用jQuery函数查询页面中所有的<buttons>元素。他返回一个有click方法的jQuery对象。click方法中有另一个匿名函数作为参数:
function(){ $("div").animate ({height:"toggle"}).append("hi"); }
这个函数查找全部的<div>元素,返回一个jQuery对象,然后第一个调用它的animate方法。animate方法的参数是用来发生动画的属性列表,这里以简写的对象方式{height:"toggle"}传递给animate。它告诉jQuery切换页面中所有的<div>元素的高度。首先,它(animate方法)减小它们(div元素)的高度至零,然后,它在把它们变回成原来的高度。
animate方法也返回jQuery对象。然后链式调用append方法,这个方法在每次按钮被按下就给<div>元素增加一个字符串"hi"。复制下面代码到JS Bin或创建一个HTML观察:
<button>Click me</button> <div style="width:100px;height:100px;background:green;"></div> <script src="http://code.jquery.com/jquery-1.8.3.js"></script> <script> $(document).ready(function(){ $("button").click(function(){ $("div").animate({height:"toggle"}).append("hi"); }); }); </script>
每次<button>被点击,绿色的<div>要么被折叠,要么被展开,同时添加一个额外的字符串"hi"。
事件驱动的麻烦
下面一段代码看似很简单:
//set h to 200 var h = 200; $(document).ready(function(){ $("button").click(function(){ //animate up to h, 200 pixels high $("div").animate({height:h}); }); });
你可能希望<div>元素扩展到200像素。但是在变量h被赋值为200到动画发生之间可能会出现很多状况。在一个复杂的jQuery应用中,变量h可能会被重用,或者你的应用的其他部分会改变他的值。你将会经盯着那几行代码,并思考到底为什么你的盒子之变换到50像素而不是200像素。可能是在你的代码某个地方你不小心改变了h的值。
老实说,这不是因为jQuery或者是其他匿名函数引起的,但总的来说,这是事件驱动编程的危害。上面的代码运行在三个不同的时间,它们第一次处理($(document).ready(...))。当文档加载($(button).cilck(...))和按钮被点击($("div").animate(...))。
那些例如用PHP写的服务端代码是连续并按照顺序执行的,从开始到结束,输出HTML生成web页面然后结束。JavaScript也可以做这些,但它在和事件联系在一起时,例如按钮点击,它才是最强大的。这就是事件驱动编程,并不只有JavaScript是这样。在智能手机app中大量使用事件驱动,他们使用Object-C 或Java或C++响应你的IPhone,Android或者Windows Phone的触摸屏事件。
如果上面的代码被翻译成Java运行在Android机,在最里层的函数中的h会导致一个错误。这是因为h没有被声明为一个全局(或在Java中是静态变量)变量,内部代码不知道它的值是什么。尽管这没有改变问题,但他至少让你致力于仔细思考如何使用变量。避免这个问题的一个快速方法是给变量加一个作用域。这个例子可以通过在第一个匿名函数中添加一个变量声明 var h 而被修复。这样这个h的优先级将高于任何全局的h。
$(document).ready (function(){ //set h to 200 var h = 200; $("button").click (function(){ //animate up to h, 200 pixels high $("div").animate ({height:h}); }); });
如果你必须使用全局配置变量,那另一个技术就是良好的命名并组织变量。同时,清晰的注释终是被推荐的:
//properties of the animation var animationConfig = {upToHeight:200}; //when document is loaded $(document).ready(function(){ //when any <button> element is clicked $("button").click(function(){ //change the height of all <div>s $("div").animate({height:animationConfig.upToHeight}); }); });
总结
这篇文章是一个关于JavaScript语法和通过jQuery来了解如何使用它的初学者指导。jQuery是一个有着特别长相的函数$和鼓励使用简写的对象,匿名函数和方法链的JavaScript库。不只是jQuery,想YUI(Yahoo User Interface)也是类似的。你现在可以直接看一个复杂的jQuery代码而不会有任何疑问或者不清楚的地方。你知道它是做什么的。由于事件驱动编程的复杂性,你可能不确定是何时做,但你知道是怎么做。