Node学习指南

978-7-115-33796-2
作者: 【美】Shelley Powers
译者: 夏思雨高亮
编辑: 陈冀康
分类: Node

图书目录:

详情

Node.js是以Google V8 JavaScript为基础的服务器端技术,具有高可扩展性,采用异步的事件驱动IO而不是线程或者独立进程。它能很好的满足那些需要频繁访问但是计算简单的网络应用的需求。本书主要向读者介绍如何使用Node这一编程框架,利用读者已有的web开发技能,编写从浏览器到Web服务器的网络应用程序。

图书摘要

版权信息

书名:Node学习指南

ISBN:978-7-115-33796-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] Shelley Powers

  译    夏思雨 高 亮

  责任编辑 陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Node.js是一套用来编写高性能网络服务器的JavaScript工具包。它可以让JavaScript在服务器端运行,因此,可用来快速构建网络服务及应用的平台。

本书是学习Node编程的入门指南。全书共16章。前4章主要介绍Node基本知识,包管理工具(npm)的安装和使用等。第5章介绍了Node处理异步开发的独特的实现方式等。第6~8章,讲解了路由、代理、Web服务器、中间件等基本概念,包括Express。第9章到第11章分别介绍了基于Redis、MongoDB以及关系型数据库的Node应用开发。第12章到第14章分别介绍了图形和媒体、Sockets.io模块、调试和测试等主题。第15章介绍了安全和权限的问题,第16章介绍了Node应用的扩展和部署。

本书适合有一定基础的JavaScript程序员阅读,也适合对学习Node应用开发感兴趣的读者学习参考。


Shelley Powers从事和编写Web技术内容超过12年了,从第一版的JavaScript发布到最新的图形设计工具。她最近的O’Reilly系列著作涉及语义网,Ajax,JavaScript和Web图形。她是一个充满激情的业余摄影师和Web开发狂热者,乐衷于在自己的很多网站上尝试最新的实验技术。

Learning Node封面上的动物是一只仓鼠(Beamys)。仓鼠有两个种类:大仓鼠属(Beamys Major)和小仓鼠属(Beamyshindei)。

仓鼠主要栖息于从肯尼亚到坦桑尼亚的非洲丛林。这种大型啮齿类动物喜欢在潮湿的环境安家:河畔或者植被茂密的地区。主要繁衍在海岸或者山地地区,尽管森林砍伐威胁到它们的自然栖息地。仓鼠住在地洞中,非常善于攀爬。

这种啮齿类动物外观非常好辨认:7~12英尺长,大概三分之一磅。头很短,全身灰色皮毛,肚皮是白色的,黑白斑驳的尾巴。和其他啮齿类动物一样,仓鼠的食物非常多样化,它们使用颊囊作食物存储。


目前正是学习Node的好时机。

Node相关的技术依然年轻充满生机,经常出现有趣的变化和改动。同时,这项技术也达到了一定的成熟度,可以确保你在学习Node上花费的时间是值得的:即使在Windows上安装也非常简单;从成百上千的可用模块中涌现出了最佳组合模块;对于产品环境来说这种结构足够健壮。

当使用Node时需要记得两个要点。第一,Node是基于JavaScript的,与你之前用于客户端开发的JavaScript多少有些类似。当然,你也可以使用另一种变形的语言,如CoffeeScript,但是JavaScript是通用的语言。

第二个需要注意的要点是,Node并不是常规的JavaScript。它是一门服务器端的技术,这意味着很多你在浏览器环境中认为应该有的功能——如保护措施——都不会出现在这里,但也会有很多其他新的不熟悉的功能。

当然,如果Node和浏览器端的JavaScript一样的话,那有什么乐趣呢?

如果你想要看Node的源码,你可以找一下Google V8的源代码。Google V8是JavaScript引擎(从技术角度来讲,是ECMAScript),也是Google Chrome浏览器的核心。那么,Node.js的一个优点是你可以只为一种JavaScript实现开发Node程序,而不是一大堆不同版本的不同浏览器。

Node被设计用于那些需要频繁I/O操作,但计算量不大的程序。更重要的是,它提供的这个功能是直接可用的。在等待一个文件加载完成或者数据库更新的过程中,不需要担心程序会阻塞其他进程,因为Node中大部分功能默认都是I/O异步的;也不需要担心线程的工作,因为Node的实现是单线程的。

 

异步I/O意味着程序并不会等待输入/输出操作处理完成之后才处理代码中的下一个步骤。第1章会介绍更多Node异步特性的细节。

更重要的一点是,Node是由很多传统Web开发人员都熟悉的语言JavaScript编写的。你会学习到如何使用新的技术,如WebSocket或者基于Express这种框架进行开发,但是至少你不需要在学习新概念的同时学习一门新的语言。对语言的熟悉使你可以只专注新的特性。

使用Node的一个挑战就是假设学习Node的部分人有Ruby或者Python背景,或者使用过Redis。我没有假定这一点,所以在解释Node组件时我不会说这就“像Sinatra一样”。

这本书唯一的假设就是读者使用过JavaScript并且喜欢它。你并不需要是个专家,但是你需要在我提到“闭包”的时候知道我在说什么,并且使用过Ajax以及对客户端环境的事件处理比较熟悉。如果你做过传统的Web开发,熟悉一些概念,如HTTP方法(GET、POST)、Web session、cookie等,你会从本书中获益良多。除了这些,你需要熟悉Windows控制台,或者Unix、Linux、MAC OSX的命令行。

如果你对一些新技术感兴趣,诸如WebSocket或者使用架构创建程序,就会喜欢这本书的。我通过这些方面向你介绍如何在现实世界使用Node。

最重要的一点,在你阅读本书时要保持思维开放,要有思想准备、你可能碰上版本不成熟的问题,也可能会撞上这种处在发展中的技术陷阱。无论如何,带着期待开始你的学习旅程吧,因为这是一个很有趣的过程。

 

如果你不确定你达到了“熟悉”JavaScript的标准,可以查看一下我对JavaScript的介绍:Learning JavaScrpt,第二版(O’Reilly)。

如果你不想按顺序阅读本书,有一些途径可供选择,这取决于你想要知道些什么以及你有多少Node经验。

如果你从来没有用过Node,建议你从第1章开始阅读至少到第5章。这几章主要讲了Node基本知识、包管理工具(npm)安装、如何使用、创建你的第一个程序以及可用的模块。第5章还涉及了一些Node中的样式问题,包括Node处理异步开发的独特实现方式。

如果你有一点Node经验,使用过内建和第三方的一些模块,也熟悉REPL(read-eval-print loop,交互控制台),可以跳过第1章到第4章,但是我建议第5章还是必读的。

在本书中,我使用了Express架构,Express又使用了Connect中间件。如果你没用过Express,请阅读第6~第8章,这几章讲解了路由、代理、Web服务器、中间件等基本概念,包括Express。尤其是如果你好奇在MVC(Model-View-Controller)框架中如何使用Express,一定要阅读第7章和第8章。

在这些基础章节之后,你可以有选择地进行阅读。比如,如果你主要使用key/value(键值对),你应该阅读第9章关于Redis的讨论。如果你对基于文档的数据感兴趣,查阅第10章,这一章介绍了如何在Node中使用MongoDB。当然,如果你只需要使用关系型数据库,可以直接跳过Redis和MongoDB到第11章,记得经常检查数据库的更新——它们可能提供了一种处理数据的新视角。

在这三章关于数据的讲解之后,我们会介绍具体程序的使用。第12章主要关注于图形和媒体访问,包括如何提供HTML5 视频媒体,以及使用Canvas和PDF文件。第13章主要讲了非常流行的Sockets.io模块,特别是如何使用新的web socket功能。

在第12章和第13章两章关于Node两种不同的具体用法介绍之后,本书末尾再次回到同一点上。当你在其他章节花费了一些时间尝试示例之后,需要读一下第14章,深入了解如何调试和测试Node程序。

第15章可能是最难的一章,也更重要。这一章主要介绍了安全和权限的问题。我不建议把这章作为你阅读的第一章,但是在你发布一个Node应用之前需要花点时间读一下这一章节。

第16章是最后一章,不管你兴趣是什么、经验多少,你都可以很放心地把它留到最后。这章主要介绍如何使你的产品上线,包括如何在流行的云服务上或者你自己的系统中部署Node程序,如何确保你的程序可以与其他Web服务器兼容,比如Apache,如何确保你的程序在崩溃或者系统重启之后自动重启。

Node与Git版本控制联系紧密,并且绝大部分(如果不是全部的话)的Node模块都在GitHub上。附录里为那些不了解Git/GitHub的人提供了教程。

虽然我之前说不需要按章节进行阅读,但是我建议你还是按顺序阅读。很多章节的工作都是在前一章的基础上完成,如果你跳跃阅读的话可能会错过一些重点。尽管本书有很多独立的程序示例,但是我主要使用了一个简单的Express程序——叫做Widget Factory,从第7章开始,在剩下的章节中都有涉及。我相信如果你从开始阅读,跳过那些你了解的小节会比跳过整个章节好很多。

就像《爱丽丝漫游奇境记》里的国王说的一样:从“开头开始,一直念到末尾,然后停止。”

本书中的示例是在Node 0.6.x不同的发布版本中创建的,大部分都在Linux环境中测试过,应该在各种Node环境中都可以正常工作。

Node 0.8.x发布的时候本书刚刚上市。大部分示例都兼容Node 0.8.x。有一些例子需要做一些修改以兼容新版Node,我在书中也有指出。

本书中的所有示例在O’Reilly官网本书主页上(http://oreil.ly/Learning_node)有压缩文件可以下载。下载解压,安装好Node之后,可以切换examples目录,输入以下命令来安装示例需要的所有依赖:

npm install–d

在第4章中我会讲到更多关于npm的用法。

以下是本书中使用的印刷体例:

文本

菜单标题,菜单选项,菜单按钮,键盘快捷键(比如Alt和Ctrl)。

等宽字体

命令,选项,switch,变量,属性,key,函数,类型,类,命名空间,方法,模块,参数,值,对象,事件,事件处理,XML标记,HTML标记,宏,文件内容或者命令行输出。

等宽粗体

显示命令或者其他应该由用户输入的文本。

等宽斜体

显示应该由用户提供的内容替换的文本。

 

这个图标表示提示、建议或者一般的记录。

 

这个图标表示一个警告或者提醒。

使用代码示例

本书有助于你完成工作。一般来说,可以在程序或者文档中使用本书的代码。关于权限的问题你并不需要联系我们,除非大量复制代码。比如,使用本书中的几段代码编写一段程序并不需要我们的允许,但是如果使用O’Reilly书中的代码买卖或者制成CD,则需要我们允许才可以。将本书中的大量代码作为你的产品文档也需要权限。

我们非常感激你在引用时标明出处,但是并不强制。引用包括标题、作者、出版商、ISBN。比如:“Learning Node by Shelley Powers (O’Reilly). Copyright 2012 Shelley Powers, 978-1-449-32307-3”。

如果你觉得你的使用不在上述范围之内,请联系我们permissions@oreilly.com。

Safari Books Online(www.safaribooksonline.com)是一个即时的电子图书馆,传递全世界在技术和商业领域著名作者的书籍和视频。

技术专家、软件开发人员、网站设计、商业和创意专家都使用Safari Books Online作为他们搜索、解决问题,学习和培训的主要资源。

Safari Books Online为组织、政府代理和个人提供了不同的产品搭配组合和定价。购买者可以访问数以千计的书籍、培训视频,以及从出版商的数据库访问还未发布的手稿,比如O’Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、MicrosoftPress、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology,等等。更多信息请访问我们的网站。

有关本书的提问和评价请联系出版商:

  O’Reilly Media, Inc.

  1005 Gravenstein Highway North

  Sebastopol, CA 95472

中国:

  北京市西城区西直门南大街2号成铭大厦C座807室(100035)

  奥莱利技术咨询(北京)有限公司

我们为本书提供了网页列出勘误表、示例,以及其他附加信息。可以访问:http://oreil.ly/Learning_node

关于本书的评价或者任何技术问题,请发送邮件到bookquestions@oreily.com。

更多有关书籍、课程、会议和最新信息,请访问我们的网站:http://www.oreily.com

Facebook:http://facebook.com/oreilly

Twitter:http://twitter.com/oreillymedia

YouTube:http://www.youtube.com/oreillymedia

感谢所有的朋友和家人在我写书的时候帮助我保持清醒。特别感谢我的编辑Simon St. Laurent,经常听我发牢骚。

还要感谢整个产品组帮助这本书从一个想法变成一个实物:Rachel Steely、Rachel Monaghan、Kiel Van Horn、Aaron Hazelton和Rebecca Demarest。

当你使用Node时,你会接收很多的帮助来自于Node.js创始人Ryan Dahl,npm创造者Issac Schlueter,也是现在的Node.js掌门人。

其他为本书提供了有用的代码和模块的是:Bert Belder、TJ Holowaychuk、Jeremy Ashkenas、Mikeal Rogers、Guillerno Rauch、Jared Hanson、Felix Geisendörfer、Steve Sanderson、Matt Ranney、Caolan McMahon、Remy Sharp、Chris O’Hara、Mariano Iglesias、Marco Aurelio、Damian Suarez、Jeremy Ashkenas、Nathan Rajlich、Christian Amor Kvalheim和Gianni Chiappetta。如果有任何漏掉没有提到的模块开发人员,我在此表示歉意。

如果没有那些好心人提供教程、做法以及有用的指导,本书还有些什么呢?感谢Tim Caswell、Felix Geisendorfer、Mikato Takada、Geo Paul、Manuel Kiessling、Scott Hanselman、Peter Krumins、Tom Hughes-Croucher、Ben Nadel,以及Nodejistu和Joyent的全体人员。


Node.js是以Google V8 JavaScript引擎为基础的服务器端技术。它具有很好的可扩展性,并使用了异步事件驱动IO,而没有使用线程或者独立进程。它能很好地满足那些需要频繁访问但是计算简单的网络应用的需求。

使用传统的Web服务器时,比如Apache,每次接收到用户对网络资源的请求时,Apache都会创建一个线程或者调用新的进程来处理。尽管Apache对请求的响应速度非常快,并在请求处理完毕后清理现场,但这种实现仍然占用了很多资源。访问频繁的网络应用会因此产生严重的性能问题。

相较而言,Node不会为每个请求创建新的进程或者线程。相反,它对特定事件进行监听,当事件发生时按需做出响应。在等待事件的过程中Node并不阻止任何请求,并且事件循环是按照先到先得的简单方式进行处理。

与编写客户端应用程序所使用的语言一样,Node应用程序也是用JaveScript编写的(或者其他可以编译成JaveScript的语言)。不过与前者不同的是,做Node应用程序开发前,首先需要搭建开发环境。

Node支持Unix/Linux、MaxOS以及Windows等多种操作系统。本章会告诉你在安装Node之前需要完成的准备工作,并带领你学习如何在Windows7和Linux(Ubuntu)系统上搭建Node开发环境。Mac系统的安装过程与Linux类似。

当搭建好开发环境后,我们会通过一个简单的Node示例应用,来说明Node中最重要的事件循环机制。

安装Node有多种方法。选择什么样的方法取决于你当前使用的开发环境,以及你希望如何使用源代码或者计划如何在你现有的应用程序中使用Node。

Windows和Mac OS都有相应的安装包,但是你也可以复制Node源代码并自行编译安装。在Windows、Linux和Mac OS环境中,你也可以使用Git的clone命令来复制Node的repo(repository,代码库)。

本节演示如何在Linux系统中(Ubuntu 10.04 VPS,或者虚拟服务器)通过编译源码搭建Node环境,同时也会演示如何在Windosw7系统上安装Node,以便配合Microsoft WebMatrix使用。

提示

可以从这个地址下载Node源代码及安装包:http://no dejs.org/#download。Wiki关于多种环境中安装Node的说明:https://github.com/joyent/node/ iki/Installing-Node-via-package-manager。鉴于Node不时会有版本更新,建议读者自己搜索最新的关于特定环境下安装Node的教程。

在Linux下安装Node之前,你需要先做一些准备工作。按照Wiki Node词条中提到的步骤首先要确认是否安装Python,如果计划使用SSL/TLS(Secure Sockets Layer,安全套接层/Transport Layer Security,传输层安全)还需要安装libssl-dev。某些Linux系统中默认安装了Python。如果没有,则可以使用系统包安装工具安装Python的稳定版本,如2.6或者2.7(Node最新版本所要求的Python版本号)。

提示

本书假定你有JavaScript和传统Web开发的经验。如果这样的话,我可能是过于谨慎了,并且在描述如何安装Node的前期准备上太唆了。

对Ubuntu和Debian来说还需要安装其他的库。大部分Debian GNU/Linux系统中都支持APT(Advanced Packaging Tool)工具,你可以用以下apt命令来确认是否已安装了需要的库:

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install build-essential openssl libssl-dev pkg-config

update命令用于确认系统上的包都更新了,upgrade命令用于升级过期的包。第三条命令用于安装需要的包。任何包依赖都由包管理工具管理。

当准备好环境后,下载Node tarball(源代码的压缩文件)。本书使用wget,你也可以使用curl。在编写本书的时候Node的最新版本是0.8.2:

wget http://nodejs.org/dist/v0.8.2/node-v0.8.2.tar.gz

下载完成后,解压:

tar -zxf node-v0.8.2.tar.gz

得到目录node-v0.6.18。进入该目录,使用以下命令编译安装Node:

./configure
make
sudo make install

考虑到某些读者之前没在Unix中使用过make命令,同此,对这三条命令简单说明一下:第一条命令首先进行依赖检查,然后根据你的系统环境和安装情况建立makefile,然后执行make命令编译最后一条命令执行安装操作。在这些命令执行后,Node即安装完毕并可以通过命令行全局访问。

提示

编程的挑战在于没有两个系统是一样的。在大部分Linux环境下这一系列安装步骤应该是能工作且可以成功的。但是,关键词是“应该”。

是否注意到最后一条命令的sudo?这是为了以root权限在Linux中安装Node。不过,你还可以用以下命令在指定的下级目录中安装Node:

mkdir ~/working
./configure --prefix=~/working
make
make install
echo 'export PATH=~/working/bin:${PATH}' >> ~/.bashrc 
. ~/.bashrc

可以看到,通过设置prefix配置选项,你可以将Node安装到本地指定路径的目录中。另外别忘记你可能还需要相应地为PATH环境变量做些更新。

提示

如果使用sudo,你需要root或者超级用户的权限。此时你的用户名必须存在于/ect/sudoers文件的列表中。

尽管可以将Node安装到本地路径,但是如果考虑到我们所有的安装步骤都是在一个共享的主机环境下进行的,是否能采用这种安装方式还需三思。因为安装Node只是第一步,接下来你还需要权限来编译Node应用程序,需要权限来让程序运行在特定端口(比如80)。事实上绝大多数共享主机环境并不会允许我们在本地路径下随意安装特定版本的Node。

除非有特殊原因,依然推荐使用sudo安装Node。

提示

在本书第4章我们会提到,使用root权限运行Node 包管理工具(npm)曾经存在的一个安全顾虑。但是,目前这些安全问题已经解决。

你可以按照之前wiki页面中的安装步骤完成Windows系统下Node的安装。但一般来说,Node只是作为Windows Web开发架构的一部分。

目前有两种Windows Web开发架构适合使用Node。一种叫做Windows Azure云平台,允许开发人员将程序托管在远端服务器(称为云)上。Microsoft提供了关于如何在Windows Azure SDK中安装Node的说明,所以本章不涉及这一过程(不过稍后会对该SDK进行说明)。

提示

Windows Azure SDK for Node安装说明:https://www.windowsazure.com/en-us/develop/nodejs/

另一种在Windows系统(本处指Windows 7)使用Node的方式是将Node与Microsoft WebMatrix集成。WebMatrix是网络开发人员用于集成开源技术的一种工具。以下是在Windows 7 WebMatrix中集成并运行Node的步骤:

1.安装WebMatrix;

2.使用最新的Windows安装包安装Node;

3.安装iisnode for IIS Express 7.x,以便让Windows上的IIS支持Node应用程序;

4.为WebMatrix安装Node模板,使用模板可以简化Node开发。

如图1-1所示,使用Microsoft Web Platform Installer安装WebMatrix,同时会安装IIS Express。IIS Express是Microsoft Web服务器的开发版本。

WebMatrix的下载地址:http://www.microsoft.com/Web/Webmatrix/

图1-1 在Windows 7中安装WebMatrix

WebMatrix安装完成后,可以使用Node官网(http://nodejs.org/#download)提供的安装包安装最新版本的Node。过程很简单,一键安装完成后打开命令行窗口输入node检查是否能正常运行,如图1-2所示。

为了能让Node与Windows下的IIS一起工作,我们需要安装iisnode。iisnode是一个本地IIS7.x模块,由Tomasz Janczuk创建并维护。它在Git Hub站点中的链接地址为:https://github.com/tjanczuk/iisnode

iisnode有x86和x64两种,但是对于x64系统,两个都需要安装。

图1-2 在Windows命令窗口中测试Node是否被正确安装

在安装iisnode的过程中,可能会碰到图1-3所示的弹出窗口,提示系统环境中未安装Microsoft Visual C++ 2010 Redistributable Package。如果你碰到了这种情况,则需要安装该组件,同时还需要保证你安装的版本与当前正在安装的iisnode的版本是兼容的:可以从x86版本(从地址http://www.microsoft.com/ download/en/details. aspx?id=5555获得)或x64版本(从地址http://www .microsoft. com/download/en/details. spx?id=14632获得)中选择安装,或者两个版本都安装。在成功安装好C++ Redistributable Package后,就可以再次运行iisnode安装程序了。

图1-3 提示对话框:需要安装C++ redistributable package

如果你还想安装iisnode的附带的示例代码,则需要以管理员权限打开命令窗口,进入到iisnode的安装目录中(“Program Files for 64- bit”或者“Program Files (x86)”)然后运行名为setupsamples.bat的批处理文件。

最后还需要为WebMatrix下载并安装Node模板,这样就完成了WebMatrix/Node的所有安装。Node模板由Steve Serson创建,可以在地址https://github.com/SteveSan derson/Node-Site-Templates-for-WebMatrix下载。

你可以通过如下步骤来测试以上工作的正确性,首先运行WebMatrix,在打开页面中选择“Site from Template”选项。然后,图1-4所示页面会打开,你可以看到两个Node模板选项:一个是“Express”(在第7章介绍),另一个是“a basic, empty site configured for Node”。选择后者,并使用“First Node Site”或者一个你喜欢的名称为新建的站点命名。

使用WebMatrix生成的新站点如图1-5所示。点击页面左上角的Run按钮,浏览器窗口会弹出并显示包含有“Hello, world!”信息的页面。

如果你在使用Windows防火墙,第一次运行一个Node应用程序时,你可能会得到一个如图1-6所示的警告信息。这时,你需要点击“Private networks”选项然后按下“Allow access”按钮,以便让防火墙知道该程序是被允许在开发机器的私有网络上通信的。

图1-4 在WebMartix中使用模板创建Node站点

图1-5 使用WebMatrix新生成的Node站点

图1-6 Windows防火墙拦截Node应用程序时的警告信息,以及允许访问所需勾选项

在新生成的WebMatrix Node工程中,存在一个名为app.js的文件。这是一个Node程序文件,包含了如下代码:

var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.end('Hello, world!');
}).listen(process.env.PORT || 8080);

我会在本章第二部分对这段代码的相关部分进行详细介绍。目前,我们只需要知道这段代码实现了一个简单的服务端,它只给客户端返回“Hello, world!”。而且我们可以在任何安装有Node环境的系统中运行这段代码,并且能得到同样的功能。

提示

若需要在WebMatrix中查看iisnode的示例,则需要在WebMatrix中选择选项“Site from Folder”,然后在弹出的对话框中输入如下内容:%localappdata%\iisnode\www。

偶数版本号代表了Node的稳定发行版本,例如当前的0.8.x版本,而奇数版本号表示Node的开发版本(当前是0.9.x)。在你有一些Node使用经验前,我建议选择稳定发行版本。

升级Node版本并不复杂。如果你使用安装包来做升级,那么旧版本的Node将自动被新版本覆盖。如果你直接使用源代码升级,为了避免潜在的混乱或者文件冲突,你始终应该先在旧版源代码目录中执行卸载命令,然后再安装新版本。在Node源代码目录中,可以运行如下make命令执行卸载:

make uninstall

下载新的源代码,编译然后安装它。当再有版本更新时,你需要再次执行以上过程。

升级Node的挑战在于,新版本的Node是否还能兼容特定的环境、模块或者Node应用程序。大多数情况下,你不会碰到版本问题。然而,如果你碰到了,你可以使用Node版本管理器(Nvm, Node Version Manager)在多个Node版本之间切换。

你可以从GitHub上下载Nvm,地址是https://github.com/creationix/nvm。与Node一样,你必须在你的系统中编译并安装Nvm。

使用Nvm安装指定版本的Node:

nvm install v0.4.1

使用如下命令切换到指定Node版本:

nvm run v0.4.1

查看可用的Node版本信息:

nvm ls

现在你已经安装了Node,是时候开始编写第一个Node应用程序了。

为了测试新的开发环境、语言或者工具,第一个写出来的程序往往是“Hello,World”。我们同样也将使用Node创建一个“Hello,World”程序,它仅仅简单的向访问它的用户输出问候语。

示例1-1包含了使用Node创建Hello,World程序需要的全部文本代码。

示例1-1 Node版Hello, World

代码被保存在名为helloworld.js的文件中。作为服务端开发使用的Node,其代码既不冗长,也不模糊。即使是一个不曾接触过Node的人,也可以直观地看出代码所表达的意思。不过可能最吸引人的是,它采用了我们很熟悉的JavaScript语言编写程序。

可以在Linux系统中使用命令行,或在Mac OS中使用终端窗口,或在Windows中使用命令窗口,运行如下命令来启动示例程序:

node helloworld.js

在程序成功运行后,会输出如下信息:

Server running at 8124

现在,你应该可以使用任何浏览器访问站点了。如果应用程序是在本地机器上运行的,可以使用localhost:8124。如果是在远程机器上运行的,需要使用远程机器的URL并访问8124端口。在浏览器中,一个显示有“Hello,World!”内容的页面将会被显示出来。到目前为止,你已经成功创建了第一个完整的并且可以正常工作的Node应用程序了。

警告

如果是在Fedora系统中安装Node环境,需要留意Node会被重命名,以避免与系统中现有功能的冲突。更多详细信息请查阅http://nodejs.tchol.org/。

由于我们没有在node命令后使用&(使应用程序在后台运行),在程序启动后,你就不能返回到命令行了。不过你依然可以继续正常访问应用程序,并且同样的信息会被显示在浏览器窗口中,直到你使用Ctrl+C来停止程序,或使用kill命令来中止node进程。

如果想在后台运行应用程序,在Linux系统中可以使用如下命令:

node helloworld.js &

之后,你需要通过“ps–ef”命令找到进程对应的ID,然后使用kill命令手动关闭该进程(比如进程ID为3747):

ps -ef | grep node
kill 3747

如果你退出终端窗口,node进程同样也会中止。

提示

在第16章中,我会讨论如何创建一个可持久的Node应用程序安装。

你不能启动另外一个监听同一端口的Node应用程序:因为在同一时间、同一端口上,只能运行一个Node应用程序。如果你的Apache工作在端口80上,你也不能在该端口上启动Node应用程序。你必须为每一个应用使用不同的端口号。

如果你在使用WebMatrix的话,也可以将helloworld.js作为一个新文件添加到之前生成的WebMatrix站点项目中。你只需要打开站点,从菜单中选择“New File…”选项,然后将示例1-1中的代码输入到创建的文件中,点击运行按钮。

警告

WebMatrix会覆盖Node程序中使用的端口号。当你运行应用程序后,你只能通过项目中定义好的端口访问站点,而不能使用在http.Server.listen方法中指定的端口。

我会在后续章节对Node应用程序进行更多剖析。但是现在,我们先来仔细看看“Hello, World”程序。

在示例1-1中,第一行代码是:

var http = require('http');

Node中的许多功能通过外部程序或库来提供,我们叫它模块(modules)。这句代码其实就是用来加载HTTP模块,然后指派给一个本地变量。HTTP模块能提供基本的HTTP功能,可以让应用程序支持对网络的访问。

下一句代码是:

http.createServer(function (req, res) { ...

在这行代码中,使用了createServer方法创建了一个新的服务器,并且传递了一个匿名函数来作为该方法时的参数。这个匿名函数就是requestListener函数,它有两个参数:一个代表服务器收到的请求(http.ServerRequest),另一个代表服务器的响应(http.ServerResponse)。

在匿名函数中,有如下代码:

res.writeHead(200, {'content-Type': 'text/plain'});

在http.ServerResponse对象中有一个writeHead方法,我们用它来发送响应信息的HTTP头,并且指定了HTTP状态码(status code)为200,同时还提供了内容类型content-type。你同样可以通过headers对象来设置其他HTTP响应头中需要的信息,例如content-length或者connection:

{ 'content-length': '123',
 'content-type': 'text/plain',
 'connection': 'keep-alive',
 'accept': '*/*' }

writeHead的第二个可选参数是reasonPhrase,用来对状态码指定文本描述。

依据下面代码将“Hello,World!”内容放入响应信息中,并发送响应:

res.end("Hello, World!\n");

调用http.ServerResponse.end方法表示本次通信已经完成,所有响应信息的头和内容均已经被发送。注意:你必须为每一个http.SErverResponse对象使用该方法。

end方法有两个参数:

这两个参数都是可选的,而且只有在字符串是非utf8编码的情况下才需要指定第二个参数,因为其默认值是utf8。

我们也可以不在end方法中传递数据块,而使用另一个write方法:

res.write("Hello, World!\n");

然后:

res.end();

下面一句代码,表示了匿名函数和createServer函数的结束:

}).listen(8124);

http.Server.listen方法紧接在createServer之后调用,用于在指定端口(本例中为8124)监听接入的客户端连接。它的可选参数是一个hostname和一个回调函数。如果指定了hostname,客户端将能通过Web地址的形式访问服务端了,比如http://oreilly.com或者http://examples.burningbird .net。

提示

本章后半部分对callback函数有更多介绍。

listen方法是异步的,这意味着在应用程序等待客户端连接建立时,不会阻塞程序的执行。listen方法之后的所有代码都会被执行。而且当连接建立起来后,一个listening事件会被触发,传给listen方法的回调函数会被执行。

最后一句代码是:

console.log('Server running on 8124/');

console是一个起源于浏览器环境并被Node采用的众多对象之一,大多数JavaScript开发人员对它都很熟悉。在此处,它提供了将文本信息输出到命令行(或者开发环境)的功能,而不再是输出信息到客户端浏览器中。

Node的基本设计原则是将应用程序放置在单线程(或单进程)中执行,同时异步处理所有事件。

考虑下典型的Web服务器(如Apache)是如何工作的。Apache可以采用两种不同的方式处理传入的请求:一种方式是将传入的每个请求分配到独立的进程中直至请求被处理完毕;另一种方式则是为每一个请求生成单独的处理线程。

第一种方式(也称为prefork multiprocessing model,或prefork MPM)可以根据Apache配置文件中指定的值创建多个子进程。使用进程的优势在于被请求的应用(如PHP应用)无需考虑线程安全问题;缺点是每个进程占用独立内存,内存消耗大,应用的扩展性也不是很好。

第二种方式(也称为worker MPM)是进程-线程混合方式。Apache为传入的每个请求创建一个新的处理线程,这样对内存的使用更加有效,但这种方式要求应用必须是线程安全的。虽然现在流行的PHP语言是线程安全的,但却无法保证和它一起被使用的各种库也是线程安全的。

不管哪种方法,它们都可以应对并发请求。如果五个用户在同一时间访问一个Web应用,并且服务器也进行了相应设置,那么Web服务器就可以同时处理五个请求。

Node的处理方式与上面两种不同。当您启动Node应用程序时,它会被创建并运行在一个单线程上。Node会等待应用程序启动完成并开始捕获请求。在未处理完当前请求时,其他请求是不能被处理的。

这种处理方式听起来并不是很有效率,如果Node是通过事件循环和回调函数实现异步运行(在Node中,事件循环一般指轮询指定事件类型并在合适的时间调用事件处理程序,而回调函数就是事件处理程序)的话,它是不应该低效的。

实际上,与一般单线程应用不同,当Node应用程序接收到用户请求时,虽然它会严格按照请求顺序初始化这些资源请求操作(如数据库请求或文件访问),但并不会一直等待操作完成或结果返回。相反,它会在操作请求中附加回调函数。当任何被请求的资源准备好或被请求的操作完成时,特定的事件会被触发,关联的回调函数也会被执行,回调函数会用请求到的资源或操作结果来做另一些事情。

如果五个用户在同一时间访问Node应用程序,并且该应用程序需要访问同一个文件中的资源时,Node会为每个文件访问请求附加一个回调函数但并不等待返回。当资源变为可用时,回调函数会被调用,最终依次满足每个用户的需求。在此期间,Node应用仍然可以处理其他同样或不同类型的用户请求。

尽管Node应用程序不是真正的并行处理用户请求,但其设计方式使得应用能繁忙且高效地处理用户请求,所以大多数人通常不会察觉到任何的响应延迟。最重要的是,它能非常有效地使用内存和其他有限的计算机资源。

为了描述Node的异步特性,示例1-2修改了之前章节使用的Hello World程序。它不再输出“Hello,World!”,而是打开先前创建的helloworld.js文件并将其内容输出给客户端。

示例1-2 异步方式地打开文件并写入数据

本示例中使用了一个新的文件系统模块(fs)。该模块对标准的POSIX文件操作进行了封装,提供了包括打开文件和访问文件内容等操作。示例1-2使用了模块中的readFile方法,并传入了多个参数,包括文件名称、文件编码方式以及匿名回调函数。

在示例1-2中,我想指出两个有关异步行为的实例,它们分别是附加在readFile方法和listen方法上的回调函数。

正如前面所讨论的,使用listen方法可以告诉HTTP server对象监听指定端口上的连接。Node不会阻塞并等待连接建立,所以如果我们需要在连接建立时做些事情,就需要提供了一个回调函数,如示例1-2所示。

当网络连接建立时会触发监听事件,该事件会触发listen方法绑定的回调函数,进而将信息输出到控制台。

第二,也是更重要的实例是附加在readFile上的回调函数。相对来说,访问文件是一个耗时的操作。如果一个单线程应用程序被多个客户同时访问,而该应用处理每一个请求时都需要进行文件访问操作的话,它可能很快就会陷入瘫痪而无法使用。

解决方法就是采用异步方式打开文件和读取文件内容。只有当内容已经读入数据缓冲区(或读取失败时),附加在readFile方法上的回调函数才会被调用。错误信息(如果有的话)和读取到的数据(如果没有错误发生时)会作为参数传送给回调函数。

在回调函数中需要进行错误检查,如果不存在错误,则将读取到的数据返回给客户端。

大多数人使用JavaScript编写客户端应用程序,这些程序只能被用户在单个浏览器中运行。而在服务端使用JavaScript编写程序可能看上去会有些古怪和陌生。创建允许多人同时访问的JavaScript服务应用可能就更让人觉得陌生了。

Node的事件循环和异步函数调用可以帮助我们,这让编写服务端JavaScript程序变得容易且更有信心。但一定要注意的是,我们正在一个新的不同以往的环境中做JavaScript开发。

为了更好地描述新环境的不同,我创建了两个新的应用:一个提供服务,另一个用于测试服务。示例1-3显示了服务程序的代码。

在代码中,一个函数被调用,以同步方式按顺序输出从1~100的数字。然后程序以类似于示例1-2的方式打开一个文件,但这次文件名是以字符串参数的形式传递给函数的。此外,程序还使用了一个定时器,文件打开操作被安排在定时器超时之后执行。

示例1-3 输出数字序列和文件内容的服务程序

输出数字的循环体起到了延迟应用程序执行的效果,以便模拟密集计算过程,该过程会引起应用程序阻塞直到计算完成。在这里setTimeout是另一个异步函数,它会紧接着调用第二个异步函数:readFile。所以该应用程序结合了异步和同步流程。

创建一个名为main.txt的文本文件,可以包含任何你想要的内容。运行应用程序并通过Chrome浏览器访问,访问时使用的url需要带有file=name的查询字段,应用程序将生成如下控制台输出:

Server running at 8124/
opening main.txt
opening undefined.txt

前两行输出信息很容易理解。第一行由程序末尾console.log输出,第二行是在文件被打开时输出的。但是,第三行的undefined.txt是怎么回事?

其实,当处理来自浏览器的Web请求时,浏览器可能会发送多个请求。例如,一般浏览器可以发送第二个请求,寻找一个叫favicon.ico的文件。正因为如此,当你在处理查询字符串时,你必须检查看看需要的数据是否被提供,并忽略没有数据的请求。

警告

当期望从查询字符串中获取某些参数时,浏览器发送多个请求的特点可能会影响到你的应用程序。因此,必须相应的调整应用,并在几个不同的浏览器上进行测试。

到目前为止,我们对Node应用程序所做的所有测试都是从浏览器中进行的。这样我们无法对其进行压力测试来体现Node应用程序的异步特性。

示例1-4是一段非常简单的测试代码。它使用HTTP模块多次向服务程序发送请求。这些请求并不是按异步方式发送的。然而,我们同时也可以使用浏览器访问该服务。两者相结合,就可以达到异步测试应用程序的目的。

提示

14章将介绍如何创建异步测试应用程序。

示例1-4 测试小程序,调用Node服务程序2000次

创建第二个文本文件,并命名为secondary.txt。内容与main.txt有显著不同即可。

在确定Node服务程序运行起来后,启动测试程序:

node test.js

在测试程序运行的同时,使用浏览器手动访问服务程序。观察服务程序在控制台的输出信息,你会看到来自浏览器的手动请求和来自测试程序的自动请求都能被处理。并且,结果与我们所期望的一致,请求到的页面中包含了如下信息:

现在,让我们尝试做一些改动。在示例1-3中,将循环体中计数用的局部变量counter改为全局变量,并重新启动应用程序。然后运行测试程序,并在浏览器中访问该页面。

输出结果显然发生改变。返回的页面内容不再是从1开始到100的数字,而是返回从类似2601和26301这样的数字开始的,按顺序排列的连续99个数字,只是初始值不同。

原因必然是因为使用了全局变量counter。因为在浏览器中手动访问页面时,自动测试程序也在做同样的事,他们都会更新counter。另外由于手动和自动测试程序的请求被按照顺序一个个的处理,因此没有争用共享数据的情况发生(在多线程环境中,并行访问共享数据同时保证线程安全是最主要的问题),如果你之前有期望输出一致的起始值,这里的结果可能会让你感到些许意外。

现在再次更改应用程序,但这次我们删除变量app之前的var关键字(“不小心的”使其成为一个全局变量)。曾几何时,在编写客户端JavaScript时,我们总是忘记使用var关键字。或许也只有当我们程序中用到的某些库使用了相同的变量名时,才会发现这种错误。

运行测试程序同时通过浏览器手动访问Node服务程序多次。你会发现浏览器得到的页面中偶尔会包含secondary.txt文件的内容,而不是期望的main.txt文件内容。这是因为在应用程序处理请求(带有文件名)和真正执行文件打开操作之间有一段时间间隔,在此间隔期间测试程序的持续访问会使得服务程序修改app变量。测试程序之所以能够引起这样的问题,是因为我们做了一个异步功能调用,在异步调用开始执行而没有完成前,Node会放弃对当前请求处理过程的控制权来处理另一个用户请求。

提示

这个示例说明了正确使用var关键字在Node中是至关重要的。

现在,你至少已经安装了一个(或多个)可用的Node版本。

你也有机会去创建多个Node应用程序,并且通过测试发现同步与异步编码之间的差异(以及不小心忘记了var关键字的情况)。

Node使用的函数调用并不都是异步的。一些对象针对同一功能可能会同时提供同步与异步版本的实现。尽管如此,如果你能尽可能多的进行异步编码,将会使Node工作得更好。

Node的事件循环和回调函数给我们带来了两大好处。

首先,应用程序可以很容易地扩展,因为执行一个单线程并不会有非常大的资源开销。如果我们用PHP创建一个类似于示例1-3的Node应用时,用户会看到相同的页面(但你的系统肯定会注意到其中的差别)。如果你在Apache中运行该PHP应用程序并默认使用prefork MPM,那么在每次应用程序接收到请求时,请求将被放在一个单独的子进程中来处理。倘若你能够拥有一个强大且高效的负载均衡系统,同时并行运行(最多)几百个子进程或许是有可能的。不过当访问量超过这个数字时,就意味着客户端需要等待响应了。

Node的第二个优势在于无需诉求于多线程开发,却达到了节约又能高效使用资源的目的。换句话说,你不必创建一个线程安全的应用程序。倘若你曾经开发过要求线程安全的应用程序,或许此时你会感到非常欣喜。

无论如何,正如前面示例应用程序所展示的那样,你不是在开发浏览器中运行的JavaScript应用程序。当你开发异步应用时,不能假设一个异步函数在另一个函数被调用前就执行完成,因为这是无法保证的(除非你在第一个回调函数中调用另一个)。此外,全局变量在Node中是非常危险的,特别是在你忘记了var关键字时。

虽然这些问题存在,但并不妨碍我们在工作使用Node,特别是考虑到它的低资源需求优势,以及不必担心线程安全问题。

提示

或许喜欢Node的原因也可以是毫无顾虑地写JavaScript代码而无需担心IE6了。


尝试使用Node编写自定义的模块或者应用程序时,并不需要每次运行写好的JavaScript文件来测试代码功能。Node有一个交互式组件称为REPL(read-eval-print-loop,读取求值列印循环),这将是本章的主题。

REPL(发音为“repple”)支持简化的Emacs风格行编辑和一小部分基本命令。在REPL中输入任何内容都与用Node运行JavaScript编写的文件具有相同的处理方式。事实上,可以使用REPL编写整个应用程序——这样就可以频繁地对程序进行测试。

本章涉及REPL的一些有趣的技巧以及如何使用这些技巧,包括如何替换浏览历史命令的底层机制以及命令行编辑等内容。

最后,如果内建的REPL不能提供你所需要的交互环境,本章的后续部分会介绍用于创建自定义REPL的API。

提示

如何使用REPL:http://docs.nodejitsu.com/articles/REPL/how-to-use- nodejs- replNodejitsu。网站提供的如何创建自定义REPL的教程:http://docs.nodejitsu.com/ articles/REPL/how-to-create-a-custom-repl。

只需要输入node命令就可以运行repl,不需要提供任何Node应用文件作参数:

$ node

REPL默认尖括号>为命令行提示符。在该符号之后输入的任何内容都由底层的V8 JavaScript引擎进行处理。

REPL的使用很简单,就像在文件中编写JavaScript一样:

> a = 2;
2

REPL可以即时打印输入的任何表达式的结果。在上面例子中,表达式的结果是2。下面这个例子中表达式结果是有三个元素的数组:

> b = ['a','b','c'];
['a','b','c']

可以使用下划线“_”调用上一个表达式。本例中,a为2,结果表达式两次自增1:

> a = 2;
2
> _ ++;
3
> _ ++;
4

还可以用下划线访问该对象的属性或者调用方法:

> ['apple','orange','lime']
[ 'apple','orange','lime']
> _.length
3
>3+ 4
7
> _.toString();
'7'

在REPL中也可以使用var关键字。可以在之后通过变量名访问表达式或者变量。但是这样可能会得到意料之外的结果。比如,在REPL中输入以下命令行:

var a = 2;

该表达式返回值并不是2,而是undefined。表达式结果为undefined的原因是变量赋值的表达式并不返回变量的值作为表达式的值。

理解以下代码,多少可以解释REPL中的这种现象:

console.log(eval('a = 2'));
console.log(eval('var a = 2'));

将上两行代码写入文件并用Node运行,返回值如下:

2
undefined

第二行代码并没有返回结果给eval,因此返回值为undefined。要记得,REPL是read-eval-print loop,重点在eval,就是求值。

但是,在REPL中你仍旧可以使用该变量,像在Node应用中一样:

> var a = 2;
undefined
> a++;
2
> a++;
3

后两条命令有返回值,由REPL打印输出。

提示

之后会说明如何创建自定义的REPL——不输出undefined,参见2.3.3节。

按Ctrl+C键两次或者Ctrl+D键一次退出REPL。2.3.1节介绍了其他退出方法。

下例是一个REPL的典型示范:

> 3 > 2 > 1;
false

这段代码很好地解释了REPL的工作原理。一眼看上去会认为期望的输出值为true,因为3大于2,2大于1。但是在JavaScript中,表达式是从左到右计算的,每个表达式的返回值作为下一个表达式的一部分进行计算。

以下REPL中的语句可以帮助你更好地理解前端代码:

> 3 > 2 > 1;
false
> 3 > 2;
true 
> true > 1;
false

现在这个结果看起来就合理多了。整个计算过程如下:首先计算表达式3>2,返回true;之后用true值与数字1进行比较。JavaScript提供了自动类型转换,true和1被认为是相等的值。因此,true不大于1,返回值为false。

REPL有助于我们发现JavaScript中这些有趣的地方。希望代码经过REPL的测试之后,应用程序中不会出现无法预测的结果(比如期望得到true却得到了false)。

你可以像写文件一样在REPL中输入JavaScript,包括导入module的require语句。以下代码显示了如何使用Query String(qs)module:

$ node
> qs = require('querystring');
{ unescapeBuffer: [Function],
 unescape: [Function],
 escape: [Function],
 encode: [Function],
 stringify: [Function],
 decode: [Function],
 parse: [Function] }
> val = qs.parse('file=main&file=secondary&test=one').file;
[ 'main', 'secondary' ]

由于没有使用var关键字,表达式的结果被直接输出,在本例中是querystring对象的接口。预期之外的收获是用这种方式不仅可以访问对象,同时还可以了解更多关于对象的可用接口。但是,如果不想看到可能出现的长文本输出,请使用var关键字:

> var qs = require('querystring');

可以用qs变量访问querystring对象的任一方法。

为了兼容外部模块,REPL可以处理多行表达式,提供了可以嵌套使用的文本标识符,跟在大括号{}之后:

> var test = function (x, y) {
... var val = x * y;
... return val;
... };
undefined
> test(3,4);
12

REPL提供重复的点“.”符号跟在开放的大括号后面表示输入命令未完成,该符号同样可以用于不闭合的小括号:

> test(4,
... 5);
20

层级间的递进需要更多的点符号。这在交互式环境中是必须的,否则会在输入过程中迷失了自己当前所在的位置:

> var test = function (x, y) {
... var test2 = function (x, y) {
..... return x * y;
..... }
... return test2(x,y);
 ...}
undefined
> test(3,4);
12
>

以下代码是一个完整的Node应用程序,可以在REPL中输入或者复制粘贴并运行:

> var http = require('http');
undefined
> http.createServer(function (req, res) {
...
...   // content header
...   res.writeHead(200, {'Content-Type': 'text/plain'});
...
...  res.end("Hello person\n");
... }).listen(8124);
{ connections: 0,
  allowHalfOpen: true,
  _handle:
  { writeQueueSize: 0,
   onconnection: [Function: onconnection],
   socket: [Circular] },
  _events:
  { request: [Function],
   connection: [Function: connectionListener] },
  httpAllowHalfOpen: false }
 >
undefined
> console.log('Server running at http://127.0.0.1:8124/
');
Server running at http://127.0.0.1:8124/
Undefined

可以通过浏览器访问该应用,这与用Node运行程序文件没有差别。而且,从REPL返回的response如上述粗体文本所示。

事实上,REPL最实用之处在于快捷查看对象。例如,Node核心对象global在Node.js官网上文档很少。为了更好地了解该对象,在REPL中将global对象传递给console.log方法,如下:

> console.log(global)

下面这行代码具有相同的结果:

> gl = global;

这里不再复制REPL中的运行结果。global对象接口非常多,你可以安装后自己尝试。这个练习的关键在于告知一种可以在任何时候简单快捷地查看对象接口的方法,不必去死记硬背需要调用什么方法、什么属性是可用的。

提示

更多关于global对象请见第3章。

在REPL中可以使用上下箭头遍历之前输入的命令。这样可以很方便地查看之前的操作,也可以一定程度上提供编辑历史命令的能力。

阅读REPL中的如下代码:

> var myFruit = function(fruitArray,pickOne) {
... return fruitArray[pickOne - 1];
... }
undefined
> fruit = ['apples','oranges','limes','cherries'];
[ 'apples',
 'oranges',
 'limes',
 'cherries' ]
> myFruit(fruit,2);
'oranges'
> myFruit(fruit,0);
undefined
> var myFruit = function(fruitArray,pickOne) {
... if (pickOne <= 0) return 'invalid number';
 ...return fruitArray[pickOne - 1];
 ... };
undefined
> myFruit(fruit,0);
 'invalid number'
> myFruit(fruit,1);
'apples'

在以上输入中没有体现出来的是,每次在修改方法检查输入值时,首先向上查找之前的命令找到函数定义,回车重新运行该方法。每添加新的语句,再用方向键重复上述操作直到完成该函数。同时也使用向上方向键重复函数调用,输出undefined。

看起来似乎多做了很多工作只是为了避免重复输入,但是如果使用正则表达式,如以下例子:

> var ssRe = /^\d{3}-\d{2}-\d{4}$/;
undefined
> ssRe.test('555-55-5555');
true
> var decRe = /^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/;
undefined
> decRe.test(56.5);
true

则在写出一个正确的正则表达式之前通常要反复修改好几次(我并不擅长正则表达式)。用REPL做正则表达式的测试非常便捷,可以避免重复输入很长的正则表达式的痛苦。

幸运的是在REPL中只需要使用方向键就可以找到创建正则表达式的命令,修改,回车然后继续测试。

除了方向键,还可以使用Tab键自动补全。例如,在命令行中输入va,按Tab键,REPL会自动补全为var。Tab也可以用于自动补全任意的全局或者局部变量。表2-1列出了一些REPL中的按键功能。

表2-1 REPL中的键盘控制

键 盘 输 入

功  能

Ctrl+C

终止当前命令。按Ctrl+C键两次直接退出

Ctrl+D

退出REPL

Tab

自动补全全局或者局部变量

向上键

查找该条命令之前的输入

向下键

查找该条命令之后的输入

下划线(_)

上一条表达式的输出

如果你担心花很多时间在REPL中编程但是结束时却没有什么可以保存下来的文件,不用担心,.save命令可以保存当前的上下文输入。这一命令和其他REPL的命令会在下一节中讲到。

REPL提供了一些常用命令的接口。在前一节中提到了.save命令,该命令将当前语境中的输入保存在文件中。除非特意创建了一个新的语境或者使用.clear命令,该文件会包含在当前REPL中所有的输入:

> .save ./dir/session/save.js

保存下来的只有你自己直接输入的文本,就像在文本编辑器中直接输入的一样。

以下是一个完整的REPL命令以及功能列表:

.break

如果多行输入发生混乱不知道当前位置时,使用.break会重新开始。不过会丢失之前输入的多行内容。

.clear

重置语境并清空所有表达式。该命令可以使你重头再来。

.exit

退出REPL。

.help

显示所有可用的REPL命令。

.save

将当前REPL会话保存至文件。

.load

将文件加载到当前会话(.load/path/to/file.js)。

如果使用REPL作为开发应用程序的编辑器,以下提示或许会有帮助:

经常使用.save命令保存当前工作。尽管当前命令可以在历史记录中查找,但是重建代码依然是个很痛苦的过程。

提到关于命令的记录,接下来就会涉及如何定制自己的REPL。

Node.js官网上关于REPL的文档提到过设置环境变量,所以可以在REPL中使用rlwrap。那么,rlwrap是什么?又为什么要在REPL中使用rlwrap呢?

rlwrap将GNU readline库的功能添加至命令行,增加键盘输入的灵活性。它监听键盘输入并提供更多的功能,比如增强行编辑以及提供命令历史浏览功能。

需要安装rlwrap和readline以便在REPL中使用这一功能,大部分Unix系统提供了简单的包安装。比如,在我的Ubuntu系统中,安装rlwrap只需要一行命令:

apt-get install rlwrap

Mac用户可以使用自己的安装工具安装该程序。Windows用户需要使用Unix环境模拟器,比如Cygwin。

下面是一个简单的示例,如何在REPL中使用rlwrap将REPL的提示改为紫色:

env NODE_NO_READLINE=1 rlwrap -ppurple node

如果希望REPL的提示符一直是紫色的,可以在bashrc文件中添加别名(alias):

alias node="env NODE_NO_READLINE=1 rlwrap -ppurple node"

同时改变提示符和颜色,命令如下:

env NODE_NO_READLINE=1 rlwrap -ppurple -S "::>" node

现在提示符变为以下符号并且是紫色的:

::>

rlwrap组件的特殊之处在于它在多个REPL窗口中浏览命令历史的功能。REPL默认只能浏览当前REPL会话的命令历史,但是使用rlwrap,在关闭当前会话下一次重新进入REPL时,不仅可以浏览当前会话的历史命令,并且可以浏览之前会话的命令历史(以及其他命令行输入)。在下面例子中,显示的命令行不是手动输入的,而是通过方向键从命令历史中找出来的:

# env NODE_NO_READLINE=1 rlwrap -ppurple -S "::>" node
::>e = ['a','b'];
 [ 'a', 'b' ]
::>3 > 2 > 1;
false

即使rlwrap如此强大,但是每次输入无返回值的表达式时依然得到undefined。然而,这一现象是可以改变的,这就是下一节中将要讨论的功能——创建自定义的REPL。

Node提供了定制REPL的功能。为了实现该功能,首先需要引入REPL模块(repl):

var repl = require("repl");

通过在repl对象上调用start方法创建新的REPL。调用该方法的语法是:

repl.start([prompt], [stream], [eval], [useGlobal], [ignoreUndefined]);

所有参数都是可选择的。如果不传入参数,将会使用各参数的默认值,参数列表如下:

prompt

Default is>.默认值为>。

stream

Default is process.stdin.默认值为process.stdin。

eval

Default is the async wrapper for eval.eval的默认值为async。

useGlobal

默认值为false,新建一个语境而不是使用全局对象。

ignoreUndefined

默认值为false。不要忽略undefined的返回值。

当我开始慢慢厌倦REPL在无返回值的表达式输出undefined时,我决定创建自己的REPL。事实上只需要两行代码就可以实现(不包括注释):

repl = require("repl");
//设置ignoreUndefined为true,启动REPL
repl.start("node via stdin> ", null, null, null, true);

在Node中运行repl.js文件:

node repl.js

然后就可以像使用REPL内建版本一样使用自定义的REPL,除了自定义的提示符以及不会在变量赋值之后再看到讨厌的undefined之外,依然可以看到除了undefined之外的其他输出。

node via stdin> var ct = 0;
node via stdin> ct++;
0
node via stdin> console.log(ct);
1
node via stdin> ++ct;
2
node via stdin> console.log(ct);
2

在代码中我希望除了提示符和ignoreUndefined以外都使用默认值。设置其他参数为null会使Node使用该参数的默认值。

可以用自定义的REPL替换eval方法。唯一的要求是有具体的格式:

function eval(cmd, callback) {
  callback(null, result);
}

stream选项比较有意思。可以运行多个版本的REPL,从标准输入(默认)或者socket中获取输入内容。Node.js网站提供的REPL文档中给出了REPL监听TCP socket的例子,代码与下面这个例子类似:

var repl = require("repl"),
   net = require("net");
//设置ignoreUndefined为true,启动REPL
repl.start("node via stdin> ", null, null, null, true);
net.createServer(function (socket) {
 repl.start("node via TCP socket> ", socket);
}).listen(8124);

在Node中运行应用程序的时候会看到标准输入提示符。还可以通过TCP进入REPL。我使用PuTTY作为Telnet客户端来登录支持TCP版本的REPL,某种程度上来说是可行的。我必须先运行.clear清理样式,但在尝试使用下划线表示上一行命令的时候,Node无法解析该符号,如图2-1所示。

图2-1 通过TCP的PuTTY和REPL并不一样

同样我也尝试过Windows 7 Telnet客户端,结果更糟糕。只有在Linux Telnet客户端使用时没有任何问题。

你可能预计到问题在于Telnet客户端的设置。然而,我并没有深究这一问题。因为从不安全的Telnet Socket运行REPL并不是我计划要做的事情,也并不推荐这一存在安全隐患的行为。就像在客户端代码中使用eval()一样,并没有破坏或者泄露客户发给你需要运行的内容,但是结果比这样更糟糕。

可以用UNIX socket通信运行REPL,比如GNU Netcat:

nc -U /tmp/node-repl-sock

可以像使用stdin一样输入命令。但是需要了解的是,如果使用TCP或者UNIX socket,任何console.log命令都会在server端打印输出,而不是客户端。

console.log(someVariable);//在server端输出

我想到一个很有用的应用程序,创建一个REPL程序,可以预加载模块。示例2-1的应用中,在REPL启动之后,http、os和util模块被加载并赋值给当前语境的对应属性。

示例2-1 创建可以预加载模块的自定义REPL

var repl = require('repl');
var context = repl.start(">>", null, null, null, true).context;
// 预加载模块
context.http = require('http');
context.util = require('util');
context.os = require('os');

用Node运行该程序,显示REPL的提示符,可以访问之前加载的那些模块:

>>os.hostname();
'einstein'
>>util.log('message');
5 Feb 11:33:15 - message
>>

如果希望像Linux中的可执行程序一样运行REPL程序,将下行代码加入应用程序开头:

#!/usr/local/bin/node

修改文件权限为可执行并运行:

# chmod u+x replcontext.js
# ./replcontext.js
 >>

Node的REPL是一个便捷的交互式工具,可以使开发任务变得简单点。REPL不仅可以在引入文件之前对JavaScript进行测试,并且可以边编写边测试直到完成时保存代码内容。

REPL另一个有用的特性是可以创建自定义的REPL,减少无用的undefined输出,预加载模块以及修改提示符或者eval方法等。

我强烈推荐在REPL中使用rlwrap,可以跨session浏览历史命令。这一特性可以节省大量的时间。话说回来,我们之中谁不喜欢更多更强大的编辑特性呢?

当你进一步探索REPL的时候,要记住本章的一个重点:

意外经常发生,频繁保存。

如果你花费很多时间在REPL中进行开发,使用rlwrap浏览历史命令,则需要频繁地保存代码。在REPL中开发与其他编辑环境一样,意外的发生不可预计。所以我一再重复:意外的发生不可避免——频繁保存为上策。

提示

REPL在Node 0.8中有较大修改,输入内建的模块名称,比如fs,就可以加载该模块了。其他一些改进标注在Node.js官网提供的新的REPL文档中。


相关图书

Node.js 后端全程实战
Node.js 后端全程实战
Node学习指南 第2版
Node学习指南 第2版
Tomcat内核设计剖析
Tomcat内核设计剖析
写给PHP开发者的Node.js学习指南
写给PHP开发者的Node.js学习指南
Node应用程序构建——使用MongoDB和Backbone
Node应用程序构建——使用MongoDB和Backbone
Node.js入门经典
Node.js入门经典

相关文章

相关课程