node.js入门手册

关于

本书致力于教会你如何用node.js来开发应用,过程中会传授你所有所需
的“高级”javascript知识。本书绝不是一本“Hello World”的教程。

状态

你正在阅读的已经是本书的最终版。因此,只有当进行错误更正以及针对
新版本Node.js的改动进行对应的修正时,才会进行更新。

本书中的代码案例都在Node.js 0.4.9版本中测试过,可以正确工作。

读者对象

本书最适合与我有相似技术背景的读者: 至少对一门诸如Ruby、python
php或者Java这样面向对象的语言有一定的经验;对javascript处于初
学阶段,并且完全是一个Node.js的新手。

这里指的适合对其他编程语言有一定经验的开发者,意思是说,本书不会
对诸如数据类型、变量、控制结构等等之类非常基础的概念作介绍。要读
懂本书,这些基础的概念我都默认你已经会了。

然而,本书还是会对JavaScript中的函数和对象作详细介绍,因为它们与
其他同类编程语言中的函数和对象有很大的不同。

本书结构

读完本书之后,你将完成一个完整的web应用,该应用允许用户浏览页面
以及上传文件。

当然了,应用本身并没有什么了不起的,相比为了实现该功能书写的代码
本身,我们更关注的是如何创建一个框架来对我们应用的不同模块进行干
净地剥离。 是不是很玄乎?稍后你就明白了。

本书先从介绍在Node.js环境中进行JavaScript开发和在浏览器环境中进
行JavaScript开发的差异开始。

紧接着,会带领大家完成一个最传统的“Hello World”应用,这也是最基础
的Node.js应用。

最后,会和大家讨论如何设计一个“真正”完整的应用,剖析要完成该应用
需要实现的不同模块,并一步一步介绍如何来实现这些模块。

可以确保的是,在这过程中,大家会学到JavaScript中一些高级的概念、
如何使用它们以及为什么使用这些概念就可以实现而其他编程语言中同
类的概念就无法实现。

该应用所有的源代码都可以通过 本书github代码仓库.

目录

. 关于
o 状态
o 读者对象
o 本书结构

. JavaScript与Node.js
o JavaScript与你
o 简短申明
o 服务器端JavaScript
o “Hello World”

. 一个完整的基于Node.js的web应用
o 用例
o 应用不同模块分析

. 构建应用的模块
o 一个基础的HTTP服务器
o 分析HTTP服务器
o 进行函数传递
o 函数传递是如何让HTTP服务器工作的
o 基于事件驱动的回调

o 服务器是如何处理请求的
o 服务端的模块放在哪里
o 如何来进行请求的“路由”
o 行为驱动执行
o 路由给真正的请求处理程序
o 让请求处理程序作出响应
. 不好的实现方式
. 阻塞与非阻塞
. 以非阻塞操作进行请求响应

o 更有用的场景
. 处理POST请求
. 处理文件上传

o 总结与展望

JavaScript与Node.js

JavaScript与你

抛开技术,我们先来聊聊你以及你和JavaScript的关系。本章的主要目的
是想让你看看,对你而言是否有必要继续阅读后续章节的内容。

如果你和我一样,那么你很早就开始利用html进行“开发”,正因如此,
你接触到了这个叫JavaScript有趣的东西,而对于JavaScript,你只会基
本的操作——为web页面添加交互。

而你真正想要的是“干货”,你想要知道如何构建复杂的web站点 —— 于
是,你学习了一种诸如php、Ruby、Java这样的编程语言,并开始书写“后
端”代码。

与此同时,你还始终关注着JavaScript,随着通过一些对jQuery,Prototype
之类技术的介绍,你慢慢了解到了很多JavaScript中的进阶技能,同时也
感受到了JavaScript绝非仅仅是window.open() 那么简单。 .

不过,这些毕竟都是前端技术,尽管当想要增强页面的时候,使用jQuery
总让你觉得很爽,但到最后,你顶多是个JavaScript用户,而非JavaScript
开发者。

然后,出现了Node.js,服务端的JavaScript,这有多酷啊?

于是,你觉得是时候该重新拾起既熟悉又陌生的JavaScript了。但是别急,
写Node.js应用是一件事情;理解为什么它们要以它们书写的这种方式来
书写则意味着——你要懂JavaScript。这次是玩真的了。

问题来了: 由于JavaScript真正意义上以两种,甚至可以说是三种形态
存在(从中世纪90年代的作为对Dhtml进行增强的小玩具,到像jQuery
那样严格意义上的前端技术,一直到现在的服务端技术),因此,很难找

到一个“正确”的方式来学习JavaScript,使得让你书写Node.js应用的时
候感觉自己是在真正开发它而不仅仅是使用它。

因为这就是关键: 你本身已经是个有经验的开发者,你不想通过到处寻
找各种解决方案(其中可能还有不正确的)来学习新的技术,你要确保自
己是通过正确的方式来学习这项技术。

当然了,外面不乏很优秀的学习JavaScript的文章。但是,有的时候光靠
那些文章是远远不够的。你需要的是指导。

本书的目标就是给你提供指导。

简短申明

业界有非常优秀的JavaScript程序员。而我并非其中一员。

我就是上一节中描述的那个我。我熟悉如何开发后端web应用,但是对“真
正”的JavaScript以及Node.js,我都只是新手。我也只是最近学习了一些
JavaScript的高级概念,并没有实践经验。

因此,本书并不是一本“从入门到精通”的书,更像是一本“从初级入门到高
级入门”的书。

如果成功的话,那么本书就是我当初开始学习Node.js最希望拥有的教程。

服务端JavaScript

JavaScript最早是运行在浏览器中,然而浏览器只是提供了一个上下文,
它定义了使用JavaScript可以做什么,但并没有“说”太多关于JavaScript
语言本身可以做什么。事实上,JavaScript是一门“完整”的语言: 它可以
使用在不同的上下文中,其能力与其他同类语言相比有过之而无不及。

Node.js事实上就是另外一种上下文,它允许在后端(脱离浏览器环境)
运行JavaScript代码。

要实现在后台运行JavaScript代码,代码需要先被解释然后正确的执行。
Node.js的原理正是如此,它使用了Google的V8虚拟机(Google的
Chrome浏览器使用的JavaScript执行环境),来解释和执行JavaScript
代码。

除此之外,伴随着Node.js的还有许多有用的模块,它们可以简化很多重
复的劳作,比如向终端输出字符串。

因此,Node.js事实上既是一个运行时环境,同时又是一个库。

要使用Node.js,首先需要进行安装。关于如何安装Node.js,这里就不赘
述了,可以直接参考官方的安装指南。安装完成后,继续回来阅读本书下
面的内容。

“Hello World”

好了,“废话”不多说了,马上开始我们第一个Node.js应用:“Hello World”。

打开你最喜欢的编辑器,创建一个helloworld.js文件。我们要做就是向
STDOUT输出“Hello World”,如下是实现该功能的代码:

console.log("Hello World");

保存该文件,并通过Node.js来执行:

node helloworld.js

正常的话,就会在终端输出Hello World 。

好吧,我承认这个应用是有点无趣,那么下面我们就来点“干货”。

一个完整的基于Node.js的web应用

用例

我们来把目标设定得简单点,不过也要够实际才行:

. 用户可以通过浏览器使用我们的应用。
. 当用户请求http://domain/start时,可以看到一个欢迎页面,页
面上有一个文件上传的表单。
. 用户可以选择一个图片并提交表单,随后文件将被上传到
http://domain/upload,该页面完成上传后会把图片显示在页面上。

差不多了,你现在也可以去Google一下,找点东西乱搞一下来完成功能。
但是我们现在先不做这个。

更进一步地说,在完成这一目标的过程中,我们不仅仅需要基础的代码而
不管代码是否优雅。我们还要对此进行抽象,来寻找一种适合构建更为复
杂的Node.js应用的方式。

应用不同模块分析

我们来分解一下这个应用,为了实现上文的用例,我们需要实现哪些部分
呢?

. 我们需要提供Web页面,因此需要一个HTTP服务器
. 对于不同的请求,根据请求的URL,我们的服务器需要给予不同的
响应,因此我们需要一个路由,用于把请求对应到请求处理程序
(request handler)
. 当请求被服务器接收并通过路由传递之后,需要可以对其进行处理,
因此我们需要最终的请求处理程序
. 路由还应该能处理POST数据,并且把数据封装成更友好的格式传
递给请求处理入程序,因此需要请求数据处理功能
. 我们不仅仅要处理URL对应的请求,还要把内容显示出来,这意味
着我们需要一些视图逻辑供请求处理程序使用,以便将内容发送给
用户的浏览器

. 最后,用户需要上传图片,所以我们需要上传处理功能来处理这方
面的细节

我们先来想想,使用PHP的话我们会怎么构建这个结构。一般来说我们
会用一个apache HTTP服务器并配上mod_php5模块。
从这个角度看,整个“接收HTTP请求并提供Web页面”的需求根本不需
要PHP来处理。

不过对Node.js来说,概念完全不一样了。使用Node.js时,我们不仅仅
在实现一个应用,同时还实现了整个HTTP服务器。事实上,我们的Web
应用以及对应的Web服务器基本上是一样的。

听起来好像有一大堆活要做,但随后我们会逐渐意识到,对Node.js来说
这并不是什么麻烦的事。

现在我们就来开始实现之路,先从第一个部分--HTTP服务器着手。

构建应用的模块

一个基础的HTTP服务器

当我准备开始写我的第一个“真正的”Node.js应用的时候,我不但不知道怎
么写Node.js代码,也不知道怎么组织这些代码。
我应该把所有东西都放进一个文件里吗?网上有很多教程都会教你把所
有的逻辑都放进一个用Node.js写的基础HTTP服务器里。但是如果我想
加入更多的内容,同时还想保持代码的可读性呢?

实际上,只要把不同功能的代码放入不同的模块中,保持代码分离还是相
当简单的。

这种方法允许你拥有一个干净的主文件(main file),你可以用Node.js
执行它;同时你可以拥有干净的模块,它们可以被主文件和其他的模块调
用。

那么,现在我们来创建一个用于启动我们的应用的主文件,和一个保存着
我们的HTTP服务器代码的模块。

在我的印象里,把主文件叫做index.js或多或少是个标准格式。把服务器
模块放进叫server.js的文件里则很好理解。

让我们先从服务器模块开始。在你的项目的根目录下创建一个叫server.js
的文件,并写入以下代码:

var http = require("http"); 
http.createServer(function(request, response) { 
response.writeHead(200, {"Content-Type": "text/plain"}); 
response.write("Hello World"); 
response.end(); 
}).listen(8888); 
搞定!你刚刚完成了一个可以工作的HTTP服务器。为了证明这一点,我 们来运行并且测试这段代码。首先,用Node.js执行你的脚本:

node server.js

接下来,打开浏览器访问http://localhost:8888/,你会看到一个写着
“Hello World”的网页。

这很有趣,不是吗?让我们先来谈谈HTTP服务器的问题,把如何组织项
目的事情先放一边吧,你觉得如何?我保证之后我们会解决那个问题的。

分析HTTP服务器

那么接下来,让我们分析一下这个HTTP服务器的构成。

第一行请求(require)Node.js自带的 http 模块,并且把它赋值给 http
变量。

接下来我们调用http模块提供的函数: createServer 。这个函数会返回
一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数,
指定这个HTTP服务器监听的端口号。

咱们暂时先不管 http.createServer 的括号里的那个函数定义。

我们本来可以用这样的代码来启动服务器并侦听8888端口:

var http = require("http"); 
var server = http.createServer(); 
server.listen(8888); 
这段代码只会启动一个侦听8888端口的服务器,它不做任何别的事情, 甚至连请求都不会应答。

最有趣(而且,如果你之前习惯使用一个更加保守的语言,比如PHP,它
还很奇怪)的部分是 createSever() 的第一个参数,一个函数定义。

实际上,这个函数定义是 createServer() 的第一个也是唯一一个参数。
因为在JavaScript中,函数和其他变量一样都是可以被传递的。

进行函数传递

举例来说,你可以这样做:

function say(word) { 
console.log(word); 
} 
function execute(someFunction, value) { 
someFunction(value); 
} 
execute(say, "Hello"); 
请仔细阅读这段代码!在这里,我们把 say 函数作为execute函数的第一 个变量进行了传递。这里返回的不是 say 的返回值,而是 say 本身!

这样一来, say 就变成了execute 中的本地变量 someFunction ,
execute可以通过调用 someFunction() (带括号的形式)来使用 say 函
数。

当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传
递这样一个变量。

我们可以,就像刚才那样,用它的名字把一个函数作为变量传递。但是我
们不一定要绕这个“先定义,再传递”的圈子,我们可以直接在另一个函数
的括号中定义和传递这个函数:

function execute(someFunction, value) { 
someFunction(value); 
} 
execute(function(word){ console.log(word) }, "Hello"); 
我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。

用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做 匿
名函数 。

这是我们和我所认为的“进阶”JavaScript的第一次亲密接触,不过我们还
是得循序渐进。现在,我们先接受这一点:在JavaScript中,一个函数可
以作为另一个函数接收一个参数。我们可以先定义一个函数,然后传递,
也可以在传递参数的地方直接定义函数。

函数传递是如何让HTTP服务器工作的

带着这些知识,我们再来看看我们简约而不简单的HTTP服务器:

var http = require("http"); 
http.createServer(function(request, response) { 
response.writeHead(200, {"Content-Type": "text/plain"}); 
response.write("Hello World"); 
response.end(); 
}).listen(8888); 
现在它看上去应该清晰了很多:我们向 createServer 函数传递了一个匿 名函数。

用这样的代码也可以达到同样的目的:

var http = require("http"); 
function onRequest(request, response) { 
response.writeHead(200, {"Content-Type": "text/plain"}); 
response.write("Hello World"); 
response.end(); 
} 
http.createServer(onRequest).listen(8888); 

也许现在我们该问这个问题了:我们为什么要用这种方式呢?

基于事件驱动的回调

这个问题可不好回答(至少对我来说),不过这是Node.js原生的工作方
式。它是事件驱动的,这也是它为什么这么快的原因。

你也许会想花点时间读一下Felix Geisend.rfer的大作Understanding
node.js,它介绍了一些背景知识。

这一切都归结于“Node.js是事件驱动的”这一事实。好吧,其实我也不是特
别确切的了解这句话的意思。不过我会试着解释,为什么它对我们用
Node.js写网络应用(Web based application)是有意义的。

当我们使用 http.createServer 方法的时候,我们当然不只是想要一个侦
听某个端口的服务器,我们还想要它在服务器收到一个HTTP请求的时候
做点什么。

问题是,这是异步的:请求任何时候都可能到达,但是我们的服务器却跑
在一个单进程中。

写PHP应用的时候,我们一点也不为此担心:任何时候当有请求进入的
时候,网页服务器(通常是apache)就为这一请求新建一个进程,并且
开始从头到尾执行相应的PHP脚本。

那么在我们的Node.js程序中,当一个新的请求到达8888端口的时候,
我们怎么控制流程呢?

嗯,这就是Node.js/JavaScript的事件驱动设计能够真正帮上忙的地方了
——虽然我们还得学一些新概念才能掌握它。让我们来看看这些概念是怎
么应用在我们的服务器代码里的。

我们创建了服务器,并且向创建它的方法传递了一个函数。无论何时我们
的服务器收到一个请求,这个函数就会被调用。

我们不知道这件事情什么时候会发生,但是我们现在有了一个处理请求的
地方:它就是我们传递过去的那个函数。至于它是被预先定义的函数还是
匿名函数,就无关紧要了。

这个就是传说中的 回调 。我们给某个方法传递了一个函数,这个方法在
有相应事件发生时调用这个函数来进行 回调 。

至少对我来说,需要一些功夫才能弄懂它。你如果还是不太确定的话就再
去读读Felix的博客文章。

让我们再来琢磨琢磨这个新概念。我们怎么证明,在创建完服务器之后,
即使没有HTTP请求进来、我们的回调函数也没有被调用的情况下,我们
的代码还继续有效呢?我们试试这个:

var http = require("http"); 
function onRequest(request, response) { 
console.log("Request received."); 
response.writeHead(200, {"Content-Type": "text/plain"}); 
response.write("Hello World"); 
response.end(); 
} 
http.createServer(onRequest).listen(8888); 
console.log("Server has started."); 
注意:在 onRequest (我们的回调函数)触发的地方,我用 console.log 输出了一段文本。在HTTP服务器开始工作之后,也输出一段文本。

当我们与往常一样,运行它node server.js时,它会马上在命令行上输出
“Server has started.”。当我们向服务器发出请求(在浏览器访问

http://localhost:8888/ ),“Request received.”这条消息就会在命令行中
出现。

这就是事件驱动的异步服务器端JavaScript和它的回调啦!

(请注意,当我们在服务器访问网页时,我们的服务器可能会输出两次
“Request received.”。那是因为大部分服务器都会在你访问
http://localhost:8888 /时尝试读取 http://localhost:8888/favicon.ico )

服务器是如何处理请求的

好的,接下来我们简单分析一下我们服务器代码中剩下的部分,也就是我
们的回调函数 onRequest() 的主体部分。

当回调启动,我们的 onRequest() 函数被触发的时候,有两个参数被传入:
request 和 response 。

它们是对象,你可以使用它们的方法来处理HTTP请求的细节,并且响应
请求(比如向发出请求的浏览器发回一些东西)。

所以我们的代码就是:当收到请求时,使用 response.writeHead() 函数
发送一个HTTP状态200和HTTP头的内容类型(content-type),使用
response.write() 函数在HTTP相应主体中发送文本“Hello World"。

最后,我们调用 response.end() 完成响应。

目前来说,我们对请求的细节并不在意,所以我们没有使用 request 对象。

服务端的模块放在哪里

OK,就像我保证过的那样,我们现在可以回到我们如何组织应用这个问题
上了。我们现在在 server.js 文件中有一个非常基础的HTTP服务器代码,
而且我提到通常我们会有一个叫 index.js 的文件去调用应用的其他模块
(比如 server.js 中的HTTP服务器模块)来引导和启动应用。

我们现在就来谈谈怎么把server.js变成一个真正的Node.js模块,使它可
以被我们(还没动工)的 index.js 主文件使用。

也许你已经注意到,我们已经在代码中使用了模块了。像这样:

var http = require("http");
...
http.createServer(...);

Node.js中自带了一个叫做“http”的模块,我们在我们的代码中请求它并把
返回值赋给一个本地变量。

这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的
对象。

给这种本地变量起一个和模块名称一样的名字是一种惯例,但是你也可以
按照自己的喜好来:

var foo = require("http");
...
foo.createServer(...);

很好,怎么使用Node.js内部模块已经很清楚了。我们怎么创建自己的模
块,又怎么使用它呢?

等我们把 server.js 变成一个真正的模块,你就能搞明白了。

事实上,我们不用做太多的修改。把某段代码变成模块意味着我们需要把
我们希望提供其功能的部分 导出 到请求这个模块的脚本。

目前,我们的HTTP服务器需要导出的功能非常简单,因为请求服务器模
块的脚本仅仅是需要启动服务器而已。

我们把我们的服务器脚本放到一个叫做 start 的函数里,然后我们会导出
这个函数。

var http = require("http"); 
function start() { 
function onRequest(request, response) { 
console.log("Request received."); 
response.writeHead(200, {"Content-Type": "text/plain"}); 
response.write("Hello World"); 
response.end(); 
} 
http.createServer(onRequest).listen(8888); 
console.log("Server has started."); 
} 
exports.start = start; 

这样,我们现在就可以创建我们的主文件 index.js 并在其中启动我们的
HTTP了,虽然服务器的代码还在 server.js 中。

创建 index.js 文件并写入以下内容:

var server = require("./server"); 
server.start(); 
正如你所看到的,我们可以像使用任何其他的内置模块一样使用server模 块:请求这个文件并把它指向一个变量,其中已导出的函数就可以被我们 使用了。

好了。我们现在就可以从我们的主要脚本启动我们的的应用了,而它还是
老样子:

node index.js 
非常好,我们现在可以把我们的应用的不同部分放入不同的文件里,并且 通过生成模块的方式把它们连接到一起了。

我们仍然只拥有整个应用的最初部分:我们可以接收HTTP请求。但是我
们得做点什么——对于不同的URL请求,服务器应该有不同的反应。

对于一个非常简单的应用来说,你可以直接在回调函数 onRequest() 中做
这件事情。不过就像我说过的,我们应该加入一些抽象的元素,让我们的
例子变得更有趣一点儿。

处理不同的HTTP请求在我们的代码中是一个不同的部分,叫做“路由选
择”——那么,我们接下来就创造一个叫做 路由 的模块吧。

如何来进行请求的“路由”

我们要为路由提供请求的URL和其他需要的GET及POST参数,随后路
由需要根据这些数据来执行相应的代码(这里“代码”对应整个应用的第三
部分:一系列在接收到请求时真正工作的处理程序)。

因此,我们需要查看HTTP请求,从中提取出请求的URL以及GET/POST
参数。这一功能应当属于路由还是服务器(甚至作为一个模块自身的功能)
确实值得探讨,但这里暂定其为我们的HTTP服务器的功能。

我们需要的所有数据都会包含在request对象中,该对象作为onRequest()
回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的
Node.JS模块,它们分别是url和querystring模块。

url.parse(string).query

|

url.parse(string).pathname |

| |

| |


http://localhost:8888/start?foo=bar&hello=world


| |

| |

querystring(string)["foo"] |

|

querystring(string)["hello"]

当然我们也可以用querystring模块来解析POST请求体中的参数,稍后
会有演示。

现在我们来给onRequest()函数加上一些逻辑,用来找出浏览器请求的
URL路径:

var http = require("http"); 
var url = require("url"); 
function start() { 
function onRequest(request, response) { 
var pathname = url.parse(request.url).pathname; 
console.log("Request for " + pathname + " received."); 
response.writeHead(200, {"Content-Type": "text/plain"}); 
response.write("Hello World"); 
response.end(); 
} 
 http.createServer(onRequest).listen(8888); 
console.log("Server has started."); 
} 
exports.start = start; 
好了,我们的应用现在可以通过请求的URL路径来区别不同请求了--这使 我们得以使用路由(还未完成)来将请求以URL路径为基准映射到处理 程序上。

在我们所要构建的应用中,这意味着来自/start和/upload的请求可以使
用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。

现在我们可以来编写路由了,建立一个名为router.js的文件,添加以下内
容:

function route(pathname) { 
console.log("About to route a request for " + pathname); 
} 
exports.route = route; 
如你所见,这段代码什么也没干,不过对于现在来说这是应该的。在添加 更多的逻辑以前,我们先来看看如何把路由和服务器整合起来。

我们的服务器应当知道路由的存在并加以有效利用。我们当然可以通过硬
编码的方式将这一依赖项绑定到服务器上,但是其它语言的编程经验告诉
我们这会是一件非常痛苦的事,因此我们将使用依赖注入的方式较松散地
添加路由模块(你可以读读Martin Fowlers关于依赖注入的大作来作为背
景知识)。

首先,我们来扩展一下服务器的start()函数,以便将路由函数作为参数传
递过去:

var http = require("http"); 
var url = require("url"); 
function start(route) { 
function onRequest(request, response) { 
var pathname = url.parse(request.url).pathname; 
console.log("Request for " + pathname + " received."); 
route(pathname); 
response.writeHead(200, {"Content-Type": "text/plain"}); 
response.write("Hello World"); 
response.end(); 
} 
http.createServer(onRequest).listen(8888); 
console.log("Server has started."); 
} 
exports.start = start; 

同时,我们会相应扩展index.js,使得路由函数可以被注入到服务器中:

var server = require("./server"); 
var router = require("./router"); 
server.start(router.route); 
在这里,我们传递的函数依旧什么也没做。

如果现在启动应用(node index.js,始终记得这个命令行),随后请求一
个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器
已经在使用路由模块了,并会将请求的路径传递给路由:

bash$ node index.js 

Request for /foo received.

About to route a request for /foo

(以上输出已经去掉了比较烦人的/favicon.ico请求相关的部分)。

行为驱动执行

请允许我再次脱离主题,在这里谈一谈函数式编程。

将函数作为参数传递并不仅仅出于技术上的考量。对软件设计来说,这其
实是个哲学问题。想想这样的场景:在index文件中,我们可以将router
对象传递进去,服务器随后可以调用这个对象的route函数。

就像这样,我们传递一个东西,然后服务器利用这个东西来完成一些事。
嗨那个叫路由的东西,能帮我把这个路由一下吗?

但是服务器其实不需要这样的东西。它只需要把事情做完就行,其实为了
把事情做完,你根本不需要东西,你需要的是动作。也就是说,你不需要
名词,你需要动词。

理解了这个概念里最核心、最基本的思想转换后,我自然而然地理解了函
数编程。

我是在读了Steve Yegge的大作名词王国中的死刑之后理解函数编程。你
也去读一读这本书吧,真的。这是曾给予我阅读的快乐的关于软件的书籍
之一。