前言

JavaScript最初设计时是为了强化Netscape 2.0浏览器的网页表现力,现在却成为了多媒体、多任务、多内核网络世界中的一种单线程语言。不过从1995年算起,JavaScript也不算是挣扎求存,倒可以算作茁壮成长。浏览器舞台上走马灯似地涌现出一个个潜在对手,你方唱罢我登场,随便举几个例子——Flash、Silverlight、Java小应用,等等。

与此同时,出现了一个叫做Ryan Dahl的程序员。他想为事件驱动型的服务器建立一个新的框架,于是深入钻研计算机科学,苦苦寻找一种动态的、单线程的语言,最终却发现答案就在眼前。于是,Node.js就此诞生,JavaScript成为了服务器世界可以倚重的力量之一。

怎么会这样呢?2001年,Paul Graham在其文章“The Other Road Ahead”1中写下了这样的话:

1. 此文章修订后的版本可见于http://paulgraham.com/road.html。最初的脚注可见于Hackers & Painters一书,该书中文版《黑客和画家》已由人民邮电出版社出版。——译者注

如果我是你,我甚至都不会碰JavaScript……我在网上看到的大多数JavaScript都没有必要存在,而且其中很多代码都跑不通。

如今,Graham是Y Combinator的首席合伙人,这家投资集团背后有Dropbox、Heroku以及数以百计正在运作的项目,几乎所有这些项目都使用了JavaScript。正如Graham在修订后的文章中言道,“JavaScript现在能用了”。

JavaScript什么时候变成了一种体面的语言?有人说,转折点是2004年Gmail的问世。Gmail向全世界证明了重量级的Ajax允许你在浏览器端运行一流的电子邮件客户端。还有人说,转折点是2006年jQuery的问世。jQuery抽象出当时浏览器对手的API,建立了事实上的标准。(截至2011年,最大的1.7万个网站中有48%使用了jQuery。2

2. 参见http://appendto.com/jquery-overtakes-flash

不管是什么原因,JavaScript就坚守在这里。Apple追随着JavaScript带来了WebKit和Safari,Microsoft追随着JavaScript带来了Metro。甚至Adobe也捧JavaScript的场,其推出的一些工具开始生成HTML5而不是Flash。JavaScript一开始只是一种微不足道的浏览器特性,现在却理所当然地成为了世界上最重要的编程语言。

感谢网络浏览器的无所不在,JavaScript比以往任何语言都更接近于兑现Java那句古老的承诺:“一次编写,到处运行。”2007年,Jeff Atwood炮制出所谓的Atwood法则:

任何可以用JavaScript写成的应用最终都会用JavaScript写。3

3. 参见http://www.codinghorror.com/blog/2007/07/the-principle-of-least-power.html

天堂里也有麻烦事儿

一般认为JavaScript是可以利用事件模型处理异步触发任务的单线程语言。如果只有两三个可能的事件,单线程语言编写的面向事件的代码要比多线程代码简单得多。这从概念上无懈可击,而且它不再需要用互斥量或信号量来封装数据以保证数据的线程安全性。但是,如果涌现出很多事件,同时要求数据的状态能够从一个事件传递到下一个事件,那么这种简单性常常要让位于令人望而却步的代码结构——金字塔厄运再次重现。

step1(function(result1) {
  step2(function(result2) {
    step3(function(result3) {
      // and so on...
    });
  });
});

“我喜欢异步编程,但我没法编出这样的代码。”这是Node.js谷歌小组中一位开发人员吐的苦水。4但问题并不在于语言,而在于程序员使用语言的方式。如何优雅地应对复杂的事件集,这仍然属于JavaScript有待解决的前沿领域。

4. 参见https://groups.google.com/forum/#!topic/nodejs/wzSUdkPICWg

那么,让我们继续向前冲吧!让我们证明给全世界看,即使是最复杂的问题也可以用整洁的、可维护的JavaScript代码来解决!

本书读者

本书是写给中级JavaScript写手的。你应该了解变量的作用域是怎么回事,诸如typeofargumentsthis之类的关键字也不会让你如坠云雾。重点中的重点也许是你要知道

func(function(arg) { return next(arg); });

只是一种啰里啰嗦、毫无必要的写法,多数情况下它可以简写成:

func(next);

(请参阅Reg Braithwaite的文章“Captain Obvious on JavaScript”5,了解更多有关JavaScript用法的重要的小例子。)

目前你无需知道JavaScript如何调度异步事件,我们留待第1章再学习。

5. 参见https://github.com/raganwald/homoiconic/blob/master/2012/01/captain-obvious-on-javascript.md

JavaScript的学习资源

随着JavaScript成为网络上的通用语(尚不论那些移动设备),涌现出了海量的参考书、课程和网站。下面是我推荐的一些学习资源。

• 如果你刚刚接触编程,请看看交互式的教程网站Codecademy6

6. 参见http://www.codecademy.com/

• 如果你此前掌握了另一门编程语言,现在想将JavaScript作为浏览器的脚本语言,则请看看CodeSchool提供的交互式jQuery空中课堂7

7. 参见http://www.codeschool.com/

• 如果你想要更正式一点的JavaScript语言介绍,请揣摩Marijn Haverbeke的Eloquent JavaScript8一书 。

8. 参见http://eloquentjavascript.net/

• 如果你只是JavaScript的初学者,想按部就班提高,避免掉入常见的陷阱,请花点时间看看JavaScript Garden9

9. 参见http://javascriptgarden.info/

如何求助

“这里应该用typeof还是instanceof呢?”对这样的问题举棋不定时,请绕开过气的W3Schools网站(遗憾的是,谷歌搜索用户很容易喜欢上它),直接访问MDN(Mozilla Developer Network,Mozilla开发人员网络)网站。10

10. 参见https://developer.mozilla.org/

Mozilla基金会(你可能听过该基金会推出的Firefox浏览器)由JavaScript之父Brendan Eich主持。该基金会知道自己要干什么。

如果你在MDN的网页上找不到答案,可以带着问题去Stack Overflow网站11。这个网站有一个特别有用的开发者社区,而且我敢打赌,任何打上JavaScript标签的纠结问题都会很快有人回应。

11. 参见http://stackoverflow.com/

运行代码示例

本书稍特别的地方在于,我会同时讨论客户端(浏览器)代码和服务器端(Node.js)代码。这也体现了JavaScript独特的可移植性。那些核心概念适用于所有的JavaScript环境,但某些示例只针对某一种JavaScript环境。

即使你对编写Node应用毫无兴趣,也希望你在本地运行一遍这些代码片段。具体说明请参见“在Node.js中运行代码”。

可以运行哪些代码示例

如果看到的代码片段带有文件名,意味着这是一段独立的代码,无需更改即可运行。这里有一个例子:

Preface/stringConstructor.js

console.log('str'.constructor.name);

上下文环境会表明该代码可运行于浏览器端、Node.js端,还是两者均可。

如果代码片段未带有文件名,则意味着这不是独立的代码,可能是大型代码例子的组成部分,或者是一段假想代码。例子如下。

var tenSeconds = 10 * 1e3;
setTimeout(launchSatellite, tenSeconds);

这些代码示例是用来阅读而不是用来运行的。

在Node.js中运行代码

Node的安装和使用都非常简单:只要访问http://nodejs.org/,单击Download(下载),并运行Windows或OS X的安装程序(或者根据*nix源进行编译)。接下来,就可以从命令行运行node以开启一个JavaScript REPL会话(类似于Ruby的irb环境)。

$ node
> Math.pow(5, 6)
15625

将JavaScript文件名作为参数传给node命令,即可运行这个JavaScript文件。

$ echo "console.log(typeof NaN)" > foo.js
$ node foo.js
number

在浏览器中运行代码

目前的各个浏览器都提供了一个很小很好的REPL工具,支持在当前页面环境下运行JavaScript代码。不过要想玩转多行代码示例,最好还是脱机使用类似于jsFiddle12这样的网络沙箱。

12. 参见http://jsfiddle.net/

使用jsFiddle,可以输入JavaScript、HTML及CSS,然后单击Run(运行)即可看到结果(或按Ctrl+Enter)。(控制台输出将传递给开发人员的控制台。)也可以启用jQuery这样的框架,为此在左边栏选中它即可。还可以保存自己的活动,这会得到一个可分享的URL。

本书的代码样式

JavaScript没有官方的样式指南,但在项目中维持一致的编码样式又很重要。为此,本书采纳了以下非常常见的约定:

• 缩进为两个空格;
• 标识符遵循驼峰式大小写命名惯例;
• 每个表达式的末尾使用分号,但函数定义除外。

根据Reg Braithwaite的提议,我对函数调用链采用了特殊的缩进规则。这条缩进规则稍显复杂,基本而言是:当且仅当调用链中的两个函数调用返回同一个对象时,才会使用相同的缩进。因此,我会写出这样的示例:

$('#container > ul li.inactive')
.slideUp();

jQuery的slideUp方法返回的对象就是调用自己的对象。于是,该函数调用不再缩进。与之相反:

var $paragraphClone = $('p:last')
  .clone();

这里的clone方法继续缩进,因为它返回了一个不同的对象。

这种约定的好处在于,它明确了调用链中各个函数的返回值。下面给出一个更复杂的示例。

$('h1')
  .first()
  .addClass('first')
.end()
  .last()
  .addClass('last');

jQuery的first方法及last方法分别筛选一个数据集而剩下其第一个及最后一个元素,而end方法撤消了last筛选方法。于是,end不再缩进,因为它返回的值等同于$('h1') 的值。(last的缩进水平可以等同于first,因为这时已重置了调用链。)

在进行函数式编程时,这种缩进方式特别有用,在第4章中将会看到这一点。

[1, 2, 3, 4, 5]
  .filter(function(int) { return int % 2 === 1; })
    .forEach(function(odd) { console.log(odd); })

关于altJS的只言片语

有很多语言能编译成JavaScript,这可以简化代码的编写。(在http://altjs.org网站上能找到一个相当全面的语言清单。)本书不谈这些。本书谈论的是,即便不使用预编译器,也能编写出最棒的JavaScript代码。我对altJS没有任何成见(请参阅下一节),但我相信重点始终是如何理解底层的语言。

某些altJS语言专门致力于“驯化”异步调用,即可以用更偏向于同步的编码风格来编写异步调用。附录1会介绍这些语言的基本情况。

CoffeeScript

我喜爱CoffeeScript,这不是什么秘密。CoffeeScript是一种编译至JavaScript的优美表述性语言。我在HubSpot的日常工作中大量使用这种语言。在一些诸如Railsconf、Øredev之类的讨论会上,我也谈到了CoffeScript。我的第一本书,CoffeeScript: Accelerated JavaScript Development,就是以CoffeeScript为主题的。13

13. 参见http://pragprog.com/book/tbcoffee/coffeescript。中文版《深入浅出CoffeeScript》已由人民邮电出版社出版。——译者注

不过在开始撰写本书时我决定,用CoffeeScript来写只会得不偿失,徒然糟践了CoffeeScript的魅力。总的来说,CoffeeScript写手对JavaScript的理解非常深刻,而诸如square = (x) => x * x 之类的代码对于JavaScript纯化论者来说不啻于远古象形文字。14

14. 好吧,也许不会一直这样,参见http://wiki.ecmascript.org/doku.php?id=harmony:arrow_function_syntax

所以,如果你是一位CoffeeScript程序员,我要为那些花括号向你致歉。至于其他人,请相信我,你从本书中学到的经验可以通行于任何altJS语言。

本书的相关资源

通过The Pragmatic Bookshelf网站上的本书页面(http://pragprog.com/book/tbajs/async-javascript),你可以下载本书中用到的示例代码,也可以获取最新的信息,还可以在一个热情友好的论坛里询问一些与本书有关的问题。

对于更常见的JavaScript相关问题,我(再次)衷心推荐Stack Overflow网站15。我跟该网站不存在什么瓜田李下之嫌,但我确实是它的热心拥趸,我在该网站的声望值已经骄傲地冲上23 000分大关(而且还继续累积着)。在这里,条清缕细、格式正规的问题几乎总能得到及时回应。

15. 参见http://stackoverflow.com/

最后,如果你希望直接联系到我,可发邮件至trevorburnham@gmail.com,或关注我的推特账号:@trevorburnham。我一直恭候着读者们的反馈。

闲言少絮。让我们异步起来吧!

目录