第2章 Node.js入门

第2章 Node.js入门

上一章介绍了如何配置你的开发环境,并讨论了一些基本的Node.js开发原则。本章将介绍怎样创建Node.js Web应用。你将会了解到JavaScript的基本特性——事件驱动,以及如何运用这一特性来搭建Node.js应用。你还将了解到Node.js的模块系统,以及如何创建你的第一个Node.js Web应用。接下来还会学习Connect模块以及它强大的中间件方法。学完本章,你将知道如何利用Connect和Node.js创建简单而强有力的Web应用。本章主要包含如下几个主题:

  • Node.js简介
  • JavaScript的闭包和事件驱动编程
  • Node.js事件驱动Web开发
  • CommonJS模块和Node.js模块系统
  • Connect Web框架简介
  • Connect的中间件模式

2.1 Node.js简介

在2009年JSConf的欧洲分会上,Ryan Dahl上台介绍了他的Node.js项目。自2008年起,Dahl开始关注当时的Web潮流并发现了Web应用运作中的奇怪之处。几年前面世的AJAX将静态网站转化为动态Web应用,但Web开发的基本构件并没有与时俱进。其问题在于当时的Web技术并不支持浏览器和服务器之间的双向通信。其中,Flickr的文件上传系统就是一个典型的例子。由于服务器无法获取所要上传的文件的具体情况,导致浏览器无法将这一上传过程在进度条上显示出来。

Dahl的想法是创建出一个能够支持从服务器到浏览器进行数据推送的Web平台,然而这一想法实施起来并不简单。在常规的Web应用中,Web平台需要支持成百乃至上千服务器到浏览器间的持续性连接。大多数平台都通过使用开销高昂的线程来处理请求。这意味着为了保持连接,一大堆空闲线程将会被打开。对此Dahl另辟蹊径。他发现使用非阻塞的socket将会节省大量的系统资源,甚至还证明了这一技术通过C语言就可以实现。但是鉴于该技术可以使用多种语言来实现,同时Dahl认为用C语言来进行非阻塞编程既冗长又乏味,于是他决定寻找一种更合适的编程语言。

Google在2008年年底发布了Chrome和新的V8 JavaScript引擎。显而易见,JavaScript的运行速度较之从前有了很大的提升。相比其他JavaScript引擎,V8最大的优点在于,在执行JavaScript代码之前,V8会将其编译成本地代码。再加上其他方面的优化性,JavaScript成为身具执行复杂任务可行性的编程语言。Dahl意识到了这一点,并决定做一个新的尝试——在JavaScript中运用非阻塞socket。他将V8引擎用C代码封装起来,创建了Node.js的第一个版本。

在获得开发社区的强烈反响之后,Dahl开始扩展Node核心。V8引擎最初的设计并不是用在服务器环境中的,因此Node.js需要对其进行扩展,以便让它能够更好地适应服务器环境。比如,浏览器通常不需要访问文件系统,但在服务器中这却是必备的功能。最终,Node.js不仅成为了JavaScript执行引擎,它还成为一个可以运行编码简单、性能高效、扩展简易的复杂JavaScript应用平台。

2.1.1 JavaScript事件驱动编程

Node.js利用JavaScript的事件驱动特性来支持平台中的非阻塞操作,这使得平台具有了超凡的性能。JavaScript是一种事件驱动的语言,这意味着如果为某一特定事件进行代码注册,当事件触发时,代码便会执行。这一理念支持无缝运行异步代码,同时不会阻塞程序其他部分的运行。

为了更好地理解这一点,我们先来看看下面这段Java代码:

System.out.print("What is your name?");
String name = System.console().readLine();
System.out.print("Your name is: " + name);


上述例子中,程序会先执行第一行和第二行,然后停下,在用户输入名字之后继续执行第三行。这就是同步编程,I/O操作会阻塞程序其余部分的运行。但是,JavaScript并不是这样运行的。

JavaScript自设计之初是为了支持浏览器操作,因此它是基于浏览器事件的。其实在很久前JavaScript就完成了这一巨大的进步,即允许浏览器将HTML的用户事件委托给相应的JavaScript代码。请看下面这段HTML代码:

What is your name?</span>
<input type="text" id="nameInput">
<input type="button" id="showNameButton" value="Show Name">
<script type="text/javascript">
var showNameButton = document.getElementById('showNameButton');

showNameButton.addEventListener('click', function() {
    alert(document.getElementById('nameInput').value);
});
// 其他代码
</script>


上述代码示例中,一个文本框和一个按钮将被创建出来。当按钮被按下时,会弹出一个包含文本框内文字的警告,用以监控addEventLister()方法这一主要功能。该方法包含两个参数,一个是事件名字,一个是在事件触发时执行的匿名函数。第二个参数通常被称为回调函数。注意,不管我们在回调函数中写了什么代码,addEventListener()方法之后的代码都会直接执行,而不用等回调函数执行完毕。

上述示例阐明了JavaScript是如何通过事件来执行指令集的。因为浏览器是单线程的,所以如果使用同步编程来实现这个例子,页面里的其他部分将会僵死,从而导致网页反应迟钝,损害用户网络体验。值得庆幸的是,JavaScript并不是这样运作的。浏览器使用内循环(inner loop),通常称之为事件轮询(event loop),操控线程来执行整个JavaScript代码。事件轮询是一个由浏览器无限期运行的单线程循环。每当触发一个事件,浏览器就将其加到事件队列。事件轮询接着从事件队列中取出下一个事件,执行事件注册对应的处理函数。事件轮询执行完所有处理函数,便继续处理下一事件,如此往复,不断推进。下图将这一过程进行了图形化:

事件轮询周期

浏览器执行的通常都是用户触发的事件(如单击按钮),而Node.js则执行着不同来源的各类事件。

2.1.2 Node.js事件驱动编程

在开发服务器逻辑时,你可能会注意到,大部分的系统资源会浪费在阻塞代码上。举个例子,请看下面这段PHP的数据库操作代码:

$output = mysql_query('SELECT * FROM Users');
echo($output);


服务器将会查询数据库并执行select语句,然后将结果返回给PHP程序,并最终将数据作为响应输出。上述代码从数据库获取到输出结果之前,会阻塞所有其他操作的执行。换言之,该进程(通常情况下是线程)在等待其他进程执行结束的过程中会一直闲置,但却要消耗系统的资源。

为了解决这个问题,许多Web平台利用线程池系统来解决连接线程占用问题。这种多线程技术非常直观,但同时又有如下几个严重不足:

  • 管理线程将成为一项复杂的工作
  • 系统资源被闲置线程占用
  • 这种应用不易于扩展

这种方法在不要求服务器和浏览器双向通信的时候是可行的。浏览器发起一个短连接请求,由服务器的响应结束连接。但如果要开发一个长期连接浏览器和服务器的实时应用呢?了解这一想法在实际应用中的情况,你可以参照下图所列举的Apache(阻塞式Web服务器)与Nginx(非阻塞式事件轮询)之间的并发请求处理性能对比。详情请查阅http://blog.webfaction.com/2008/12/alittle-holiday-present-10000-reqssec-with-nginx-2/

图像说明文字

Apache和Nginx的并发处理性能对比

上图可以看出,Apache请求响应性能的降低速度明显快于Nginx。但接下来这张二者内存消耗的对比图中,你将看到Nginx的事件轮询架构对服务器内存消耗的巨大影响。

Apache和Nginx处理并发请求时的内存占用对比

从上面的对比中,可以得出这样一个结论:使用事件驱动架构可以有效降低服务器负载,而在开发Web应用时利用JavaScript异步的理念则可达到立杆见影的效果。而使这一方法具有可行性的,则是被JavaScript开发人员称为闭包的简单设计模式。

2.2 JavaScript闭包

闭包是赋给其父环境中某个变量的函数。使用闭包模式,父函数中的变量作用域将会延伸绑定到闭包函数。如下所示:

function parent() {
    var message = "Hello World";

    function child() {
        alert (message);
    }

    child();
}

parent();


上述例子可以看出, child()函数中依然可以访问parent()内定义的变量。这个例子比较简单,下面让我们看看这段更有趣的代码:

function parent() {
    var message = 'Hello World';

    function child() {
        alert (message);
    }
    return child;
}

var childFN = parent()
childFN();


这次就不一样了,parent()函数直接返回了child()函数,在parent()执行完成之后,child()函数依然可以继续调用。对于有些开发人员来讲,这点可能有点不合乎自己既往的编程经验,作为parent()的局部变量不是只有在它执行的时候才存在吗?而这一特性就是闭包的精粹所在。闭包绝不是普通的函数,它还包含了函数创建环境。上述示例中,childFN()就是一个闭包对象,该对象既包含child()函数,还包括创建该函数时的环境变量,比如message变量。

闭包在异步编程中至关重要,因为JavaScript函数是可以作为参数传递给其他函数的一类对象。这表明,你可以创建一个回调函数,并将其作为参数传给事件处理程序。当事件触发的时候,回调函数就会被调用,就算创建回调函数的父函数已经执行完毕,回调函数依然可以操作父环境中的任一变量。因此,当你在进行事件驱动开发时,就不需要将作用域内所有的状态都传给事件处理程序。

2.3 Node模块

一些独一无二的特性使得JavaScript成为一个既保持了高性能又不失可维护性的强大程序设计语言。实践应用证明闭包和事件驱动是非常行之有效的。但是像其他所有的编程语言一样,JavaScript也是不尽完美的,它最主要的设计缺陷在于,共用一个全局命名空间。

让我们从JavaScript的起源——浏览器,来分析一下这个问题存在的原因。在浏览器里,在页面中加载一个脚本,引擎将会把代码注入到一个所有脚本共享的地址空间中,这意味着如果你在一个脚本中为某个变量赋值,先前脚本中已定义的同名变量将会被覆盖。这在小规模的程序中没什么问题,但在大型的应用中却容易出现冲突,而且错误也变得难以追踪。这是Node.js演变为一个平台的重要障碍。好在CommonJS的模块标准为这一问题提供了解决方案。

2.3.1 CommonJS模块

CommonJS始于2009年,旨在将运行在浏览器之外的JavaScript进行标准化。自诞生以来,CommonJS已经解决了大量的JavaScript问题,其中包括通过对编写以及包含独立JavaScript模块进行规范而解决的全局命名空间问题。

CommonJS模块指定了如下几个在模块中会用到的关键组件。

  • require():用来将模块加入到应用中。
  • exports:这个对象在每个模块中都有,当模块加载后,它可以提供对某一段代码的访问。
  • module:这个对象原本是用于提供模块中的元数据信息,它还包含一个指向exports对象指针的属性。不过在常规应用中,exports对象多被用作独立对象,使得module的应用场景也随之变化。

Node对CommonJS模块的实现中,每个模块都会被写进一个单独的JavaScript文件中,而且每个模块都有其独立的作用域来容纳他们所有的变量。模块开发人员可以将模块内的功能用 exports 对象进行展示。为了更好地理解,请参看下面所举的例子。创建一个hello.js文件,输入如下代码:

var message = 'Hello';

exports.sayHello = function(){
  console.log(message);
}


再创建一个名为server.js的应用程序文件,输入如下代码:

var hello = require('./hello');
hello.sayHello();


上述例子中,一个名为hello的模块被创建出来。该模块中包含一个名为message的变量。该变量独立于hello模块,整个模块只有sayHello()方法可以作为exports对象的属性暴露给用户。server.js应用程序文件使用require()方法来加载hello模块,以便能够调用hello模块的sayHello()方法。

另一种创建模块的方法是利用 module.exports 来暴露单个函数。为了更好地理解,我们将修改上述示例中的hello.js文件,如下所示:

module.exports = function(){
  var message = 'Hello';

  console.log(message);
}


然后server.js中模块加载方法也要进行相应修改:

var hello = require('./hello');
hello();


经过一番修改,应用程序文件server.js中可以直接使用hello模块,而不需要调用作为模块属性的sayHello()方法。

CommonJS的模块规范使Node.js平台可以进行无限扩展,又不会影响Node的核心模块。没有它,Node.js平台将会成为一堆杂乱无章的冲突。然而,并不是所有的模块都是同一类,在开发Node应用时,通常会碰到好几种模块。

提示文字

在加载模块时可以省掉.js扩展名,Node会先寻找同名的文件夹,如果找不到,则寻找同名的js文件。

2.3.2 Node.js核心模块

核心模块指的是那些被编译进Node的二进制文件中的模块。它们被预置在Node中,Node的官方文档中也对其进行了详细的介绍。核心模块提供了大多数Node的基本功能,比如文件系统访问、HTTP和HTTPS接口等。要加载核心模块,直接在代码文件中使用require()方法即可。下面我们来看一个利用核心模块fs读取系统hosts文件的例子,代码如下:

fs = require('fs');

fs.readFile('/etc/hosts', 'utf8', function (err, data) { 
  if (err) { 
    return console.log(err);
  }

  console.log(data);
});


代码中包含fs模块时,Node将自动在核心模块文件夹中加载,然后就可以使用fs.readFile()读取文件内容,并在命令行输出中打印出来。

提示文字`

了解更多关于核心模块的信息,请参阅http://nodejs.org/api/中的官方文档。

2.3.3 Node.js第三方模块

在上述章节中我们已经讲述了如何使用NPM安装第三方模块。NPM会将模块安装到应用根目录下的node_modules文件夹中,然后你就可以像使用核心模块一样使用第三方模块了。在进行模块加载时,Node会先在核心模块文件夹中进行搜索,然后再到node_modules文件夹中的模块文件夹中查找。关于如何使用express模块,如下所示:

var express = require('express');
var app = express();


Node会自动到node_modules文件夹中找到express模块并完成加载,然后你就可以把它作为一种方法来生成express应用对象。

2.3.4 Node.js文件模块

上述例子已经讲述了如何使用Node直接从一个文件中加载模块。不过这些例子只提供了从当前目录获取模块文件的方法。实际上,我们可以将文件放在任何位置,只要在加载模块文件时加上路径即可。回到上述hello.js和server.js的例子,在这两个文件所在的目录中,创建一个名为modules的文件夹,并将hello.js移动到新建的文件夹中,那么server.js就需要通过使用一个相对路径来加载hello.js了,代码如下:

var hello = require('./modules/hello');

当然,也可以使用绝对路径:

var hello = require('/home/project/frist-example/modules/hello');


Node会根据提供的路径进行加载。

2.3.5 Node.js文件夹模块

虽然并不是所有的Node开发人员都会去编写第三方的模块,但是这里还是要讲述一下如何从文件夹中加载模块。文件夹模块的加载方法与文件模块是一致的,如下所示:

var hello = require('./modules/hello');


如果modules目录下存在一个名为hello的文件夹,Node便会在hello文件夹中搜索package.json,如果找到了,Node将尝试解析它,并查找是否存在一个main属性。比如,像下面这种格式的package.json:

{
  "name" : "hello",
  "version" : "1.0.0",
  "main" : "./hello-module.js"
}

Node将会去加载 ./hello/hello-module.js这个文件。如果package.json文件不存在,或者没有定义main属性,Node默认会去加载./hello/index.js文件。

对于编写复杂的JavaScript应用,Node的模块机制的确是一剂良方。它可以有效地帮助开发人员进行代码组织。同时,使用NPM还可以方便地查找和安装由社区创造的大量第三方模块。Ryan Dahl构建一个更为完美的Web框架的想法最终以一个提供各种解决方案的Node.js平台告终。不过现在它还只是一个平台,直到express这个第三方模块的出现,一个完美的Web框架才最终成型。

2.4 Node.js Web应用开发

Node.js作为一个平台,可以用于开发各类应用程序,其中最常用的是Web应用开发。Node的代码依赖模式将这一任务交给进行第三方模块开发的社区,模块叠模块。来自全球各地的公司和个人开发者加入到了这一行列,扩展Node的核心API,给应用开发带来一个更好的起点。

在多个支持Web应用开发的模块中,最有名的当属Connect模块。以Node底层API为核心,Connect整合了一系列包装程序用以Web应用框架的开发。为了理解Connect,让我们先来看一个最基本的Node Web服务器的例子。创建一个名为server.js的文件,输入如下内容:

var http = require('http');

http.createServer(function(req, res){
  res.writeHead(200, {
    'Content-Type': 'text/plain'
  });
  res.end('Hello World');
}).listen(3000);

console.log('Server running at http://localhost:3000/');

启动上面刚刚完成的Web服务器也很简单,使用命令行工具,进入到server.js文件所在的目录,然后执行如下命令:

$ node server

然后用浏览器打开http://localhost:3000/,即可以看到Web服务器的响应:Hello World。

这其中的工作原理是什么?上述例子中,http模块创建了一个轻量级的Web服务器,用以监听3000端口。第一步,向http模块发出请求;第二步,调用createServer()方法创建一个监听3000端口的server对象。注意,这里传了一个回调函数给createServer()方法。

当Web服务器收到HTTP请求时,回调函数便会执行。server对象会将reqres两个参数传给回调函数,这两个参数包含有响应HTTP请求所需的信息和功能函数。回调函数将执行如下两步。

1. 首先是调用response对象的writeHead()方法。该方法用来设置HTTP响应标头。在本例中设置了Content-Type的标头值为text/plain。如果响应的内容是HTML,则要用html/plain替代这里的text/plain

2. 接着,调用 response对象的end()方法。该方法用于完成响应。这里是将单个字符串作为参数传入end(),作为HTTP响应的主体。另一种更为常用的做法是先用write()增加主体内容,再用end()完成响应,如下所示:

res.write('Hello World');
res.end();


上述例子演示了如何用Node底层API来实现一些必要的基本功能。然后这也仅仅是个例子罢了,若要通过调用底层API完成一个功能齐全的Web应用,将需要编写大量的补充性代码才能实现那些基本需求。好在一个叫Sencha的公司开发了Connect模块,帮助完成了很多基础性的工作。

初识Connect模块

Connect是一个拦截所有请求,并以一种更模块化的方法进行拦截处理的模块。在上述Web服务器例子中,我们已经演示了如何利用http模块来构造一个Web服务器。如果想扩展这个例子,那么就需要编写代码来管理服务器所收到的各类HTTP请求,进而对这些请求进行适当的处理,并且逐个响应。

为实现这一目的,Connect提供了API。Connect使用名为中间件的模块化组件,来简化在预定义的HTTP请求情景上进行的应用逻辑注册。Connect中间件基本上以回调函数为主,当HTTP请求出现时便开始执行。中间件会首先进行一些逻辑处理,然后对请求进行响应,或者调用下一个注册了的中间件。Connect中也包含一些比较常用的中间件,比如日志工具、静态文件服务工具等,因此你只需要按照你的应用需求进行中间件自定义即可。

Connect使用dispatcher(调度器)对象负责处理服务器收到的每个HTTP请求,并以级联的方式组织好中间件,依次执行。为了更好地理解Connect,请参看下图。

Connect处理流程图

上图中Connect处理了两个不同的请求,第一个由一个自定义的中间件进行处理,第二个由静态文件中间件进行处理。Connect的调度器启动了处理流程,并使用next()方法来调用下一个处理程序。直到某一个中间件使用res.end()方法完成了响应,整个请求才算处理完毕。

下一章中将介绍如何使用Express,其实Express就是基于Connect的。因此,为了便于了解Express,我们从理解Connect开始,首先创建一个Connect应用。

创建一个名为server.js的文件,并输入如下代码:

var connect = require('connect');
var app = connect();
app.listen(3000);

console.log('Server running at http://localhost:3000/');

如你所见,上述代码通过使用connect模块创建了一个Web服务器。但是,Connect并不是核心模块,所以要用NPM进行安装。正如上文所述,安装第三方模块有多种方法。其中最简便的方法是直接调用npm install命令。要想使用该命令进行安装,首先请打开命令行工具,进入到刚刚创建的server.js所在的目录,执行如下命令:

$ npm install connect


NPM便会将Connect安装到node_modules目录中,这样便可在程序中向它发送请求。运行适才创建好的Web服务器,执行如下命令即可:

$ node server

Node将启动上文中创建好的服务器,并且用console.log()语句打出服务器的状态。你可以在浏览器中尝试访问http://localhost:3000/。不出意外的话,你将会看到如下界面:

图像说明文字

Connect应用空响应

该响应表明应用里目前还没有任何中间件来响应HTTP GET请求。这意味着你需要注意以下两点:

  • 你已经成功安装并使用了Connect模块
  • 你应该开始着手编写Connect中间件了

1. Connect中间件

Connect中间件其实就是拥有某一特定功能的JavaScript函数。每个中间件都有如下三个参数。

  • req:包含所有HTTP请求信息的对象。
  • res:包含所有HTTP响应的信息,并可以通过它来设置响应的各种属性。
  • next:指向Connect中间件级联中下一个中间件函数。

定义好一个中间件后,只需要使用app.use()注册即可。下面我们将对前面所讲述的例子进行扩充。编写一个中间件,修改server.js如下:

var connect = require('connect');
var app = connect();

var helloWorld = function(req, res, next) {
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
};
app.use(helloWorld);

app.listen(3000);
console.log('Server running at http://localhost:3000/');


然后用下面的命令再次启动服务器:

$ node server

再次访问一下http://localhost:3000,你将会看到如下界面:

图像说明文字

Connect应用的响应

大功告成,第一个Connect中间件创建完毕!

下面简明重述一下Connect中间件的创建过程。首先,添加一个名为helloWorld()的中间件函数,并传入reqresnext三个参数。该自定义的中间件使用res.setHeader()方法设置了Content-Type标头,然后用res.end()设置了响应内容,最后使用app.use()在Connect应用中注册了这一中间件。

2. 理解Connect中间件的执行顺序

Connect最大的特点之一便是可以注册任意数量的中间件。通过使用app.use()方法,可以将中间件函数连成一串,进而在程序开发时最大限度地保证其灵活性。在执行的过程中,Connect将下一个要执行的中间件函数以next参数的方式传给即将执行的中间件函数。在每一个中间件函数中,都可以决定到底是立即结束执行,还是继续执行下一个中间件函数。要注意的是,中间件函数的执行遵循先进先出(FIFO,first-in-first-out)的顺序,直到所有的中间件函数都执行完毕,或者某个中间件函数没有调用`next 方法。

为了更好地理解,下面将前面的例子添加一个名为logger的函数,用以将服务器所收到的所有的请求打印到命令行中。将server.js进行如下修改:

var connect = require('connect');
var app = connect();

var logger = function(req, res, next) {
  console.log(req.method, req.url);

  next();
};

var helloWorld = function(req, res, next) {
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
};

app.use(logger);
app.use(helloWorld);
app.listen(3000);

console.log('Server running at http://localhost:3000/');


上述代码段中,增加了一个名为logger的中间件,使用console.log方法简单地将请求信息打印在了终端上。注意logger中间件注册在helloWorld()中间件之前,这决定了各个中间件的执行顺序。另外,在logger中还调用了next(),用以调用helloWorld()中间件。如果将next()这行删除,那么程序执行到logger中间件便会停下来。然而此时还没有调用res.end()方法,请求便会永远得不到响应,一直处于挂起状态。

为了测试所做修改,使用如下命令再次启动服务器即可:

$ node server

接下来访问http://localhost:3000/。命令行工具中会输出浏览器的请求信息。

3. Connect中间件的加载

你可能已经注意到了,不管请求Web服务器的哪个路径,你所注册的中间件都会执行。这并不符合如今的Web应用开发习惯——针对不同路径作出不同响应。Connect通过加载来满足这一需求,可以让你根据不同的请求路径来确定中间件执行与否。在app.use()方法中传入路径即可开启加载功能。为了更好地理解,我们回到前面的例子。请参照如下示例修改server.js:

var connect = require('connect');
var app = connect();

var logger = function(req, res, next) {
  console.log(req.method, req.url);
  next();
};

var helloWorld = function(req, res, next) {
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
};

var goodbyeWorld = function(req, res, next) {
  res.setHeader('Content-Type', 'text/plain');
  res.end('Goodbye World');
};

app.use(logger);
app.use('/hello', helloWorld);
app.use('/goodbye', goodbyeWorld);
app.listen(3000);

console.log('Server running at http://localhost:3000/');


上述代码中有几处修改,一是加载的中间件helloWorld()将仅响应路径为/hello的请求。二是增加了一个稍显怪异的goodbyeWorld()中间件,它将仅响应路径为/goodbye的请求。logger中间件并没有修改,它依然是响应所有的请求。另外要注意的是,这几个中间件都不会响应对基本路径的请求,因为我们把中间件helloWorld()加载到了一个指定的路径。

Connect是一个很强大的模块,它支持了常规Web应用的大量特性。Connect的中间件非常简单而又颇具JavaScript风格。它既可以帮你无限扩展应用逻辑,又不会打破Node平台的敏捷哲学。它既可以有效改善Web应用的基础架构,又刻意缺失了其他Web框架的一些基本功能,其原因在于它遵循了Node社区的一个基本理念:创建精简的模块,让其他人在你创建的模块的基础上创建新的模块。整个社区的人都想在Connect的基础之上创建自己的Web基础架构,最终,一个名叫TJ Holowaychuk的天才程序员的作品赢得了大多数人的认可,这便是如今已经无人不知的基于Connect的Web框架——Express。

2.5 总结

本章介绍了Node.js如何利用JavaScript的事件驱动特性,以及如何使用CommonJS的模块系统来扩展其核心功能。还介绍了Node.js Web应用的基本原理和Connect模块。此外还学习了如何创建Connect应用,以及如何使用中间件函数。下一章将讨论基于Connect的Web框架——Express。

目录

  • 版权声明
  • 致谢
  • 前言
  • 第1章 MEAN简介
  • 第2章 Node.js入门
  • 第3章 使用Express开发Web应用
  • 第4章 MongoDB入门
  • 第5章 Mongoose入门
  • 第6章 使用Passport模块管理用户权限
  • 第7章 AngularJS入门
  • 第8章 创建MEAN的CURD模块
  • 第9章 基于Socket.io的实时通信
  • 第10章 MEAN应用的测试
  • 第11章 MEAN应用的调试与自动化