NodeJS

1. Node.js 最大的特点就是采用异步式 I/O 与事件驱动的架构设计。对于高并发的解决方
案,传统的架构是多线程模型,也就是为每个业务逻辑提供一个系统线程,通过系统线程切
换来弥补同步式 I/O 调用时的时间开销。Node.js 使用的是单线程模型,对于所有 I/O 都采用
异步式的请求方式,避免了频繁的上下文切换。Node.js 在执行的过程中会维护一个事件队
列,程序在执行时进入事件循环等待下一个事件到来,每个异步式 I/O 请求完成后会被推送
到事件队列,等待程序进程进行处理。


2. 小技巧——使用 supervisor
如果你有 PHP 开发经验,会习惯在修改 PHP 脚本后直接刷新浏览器以观察结果,而你
在开发 Node.js 实现的 HTTP 应用时会发现,无论你修改了代码的哪一部份,都必须终止
Node.js 再重新运行才会奏效。这是因为 Node.js 只有在第一次引用到某部份时才会去解析脚
本文件,以后都会直接访问内存,避免重复载入,而 PHP 则总是重新读取并解析脚本(如
果没有专门的优化配置)。Node.js的这种设计虽然有利于提高性能,却不利于开发调试,因
为我们在开发过程中总是希望修改后立即看到效果,而不是每次都要终止进程并重启。
supervisor 可以帮助你实现这个功能,它会监视你对代码的改动,并自动重启 Node.js。
使用方法很简单,首先使用 npm 安装 supervisor:
$ npm install -g supervisor


3. 什么是阻塞(block)呢?线程在执行中如果遇到磁盘读写或网络通信(统称为 I/O 操作),
通常要耗费较长的时间,这时操作系统会剥夺这个线程的 CPU 控制权,使其暂停执行,同
时将资源让给其他的工作线程,这种线程调度方式称为 阻塞。当 I/O 操作完毕时,操作系统
将这个线程的阻塞状态解除,恢复其对CPU的控制权,令其继续执行。这种 I/O 模式就是通
常的同步式 I/O(Synchronous I/O)或阻塞式 I/O (Blocking I/O)。
相应地,异步式 I/O (Asynchronous I/O)或非阻塞式 I/O (Non-blocking I/O)则针对
所有 I/O 操作不采用阻塞的策略。当线程遇到 I/O 操作时,不会以阻塞的方式等待 I/O 操作
的完成或数据的返回,而只是将 I/O 请求发送给操作系统,继续执行下一条语句。当操作
系统完成 I/O 操作时,以事件的形式通知执行 I/O 操作的线程,线程会在特定时候处理这个
事件。为了处理异步 I/O,线程必须有事件循环,不断地检查有没有未处理的事件,依次予
以处理。
阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞
模式下,一个线程永远在执行计算操作,这个线程所使用的 CPU 核心利用率永远是 100%,
I/O 以事件的方式通知。在阻塞模式下,多线程往往能提高系统吞吐量,因为一个线程阻塞
时还有其他线程在工作,多线程可以让 CPU 资源不被阻塞中的线程浪费。而在非阻塞模式
下,线程不会被 I/O 阻塞,永远在利用 CPU。多线程带来的好处仅仅是在多核 CPU 的情况
下利用更多的核,而Node.js的单线程也能带来同样的好处。这就是为什么 Node.js 使用了单
线程、非阻塞的事件编程模式。
单线程事件驱动的异步式 I/O 比传统的多线程阻塞式 I/O 究竟好在哪里呢?简而言之,
异步式 I/O 就是少了多线程的开销。对操作系统来说,创建一个线程的代价是十分昂贵的,
需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU 的缓存被
清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性


4. 我们经常把 Node.js 的模块和包相提并论,因为模块和包是没有本质区别的,两个概念
也时常混用。如果要辨析,那么可以把包理解成是实现了某个功能模块的集合,用于发布
和维护。对使用者来说,模块和包的区别是透明的,因此经常不作区分


5. 不可以通过对 exports 直接赋值代替对 module.exports 赋值。
exports 实际上只是一个和 module.exports 指向同一个对象的变量,
它本身会在模块执行结束后释放,但 module 不会,因此只能通过指定
module.exports 来改变访问接口。


6. 包是在模块基础上更深一步的抽象,Node.js 的包类似于 C/C++ 的函数库或者 Java/.Net
的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Node.js 根
据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求。
Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件 package.json。严格符
合 CommonJS 规范的包应该具备以下特征:
 package.json 必须在包的顶层目录下;
 二进制文件应该在 bin 目录下;
 JavaScript 代码应该在 lib 目录下;
 文档应该在 doc 目录下;
 单元测试应该在 test 目录下。
Node.js 对包的要求并没有这么严格,只要顶层目录下有 package.json,并符合一些规范
即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守 CommonJS 规范。


7. Node.js 在调用某个包时,会首先检查包中 package.json 文件的 main 字段,将其作为
包的接口模块,如果 package.json 或 main 字段不存在,会尝试寻找 index.js 或 index.node 作
为包的接口。


8. 使用全局模式安装的包并不能直接在 JavaScript 文件中用 require 获
得,因为 require 不会搜索 /usr/local/lib/node_modules/。
总而言之,当我们要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要
在命令行下使用,则使用全局模式安装。


9. npm 提供了一个有趣的命令 npm link,它的功能是在本地包和全局包之间创建符号链
接。我们说过使用全局模式安装的包不能直接通过 require 使用,但通过 npm link命令
可以打破这一限制。举个例子,我们已经通过 npm install -g express 安装了 express,
这时在工程的目录下运行命令:
$ npm link express
./node_modules/express -> /usr/local/lib/node_modules/express
我们可以在 node_modules 子目录中发现一个指向安装到全局的包的符号链接。通过这
种方法,我们就可以把全局包当本地包来使用了。
npm link 命令不支持Windows。


10. Node.js 支持命令行下的单步调试.
在命令行下执行 node debug debug.js,将会启动调试工具.
V8 提供的调试功能是基于 TCP 协议的,因此 Node.js 可以轻松地实现远程调试。在命
令行下使用以下两个语句之一可以打开调试服务器:
node --debug[=port] script.js
node --debug-brk[=port] script.js


node --debug 命令选项可以启动调试服务器,默认情况下调试端口是 5858,也可以
使用 --debug=1234 指定调试端口为 1234。使用 --debug 选项运行脚本时,脚本会正常
执行,但不会暂停,在执行过程中调试客户端可以连接到调试服务器。如果要求脚本暂停执
行等待客户端连接,则应该使用 --debug-brk 选项。这时调试服务器在启动后会立刻暂停
执行脚本,等待调试客户端连接。
当调试服务器启动以后,可以用命令行调试工具作为调试客户端连接,例如:
//在一个终端中
$ node --debug-brk debug.js
debugger listening on port 5858
//在另一个终端中
$ node debug 127.0.0.1:5858
connecting... ok
debug> n
break in /home/byvoid/debug.js:2
1 var a = 1;
2 var b = 'world';
3 var c = function (x) {
4 console.log('hello ' + x + a);
debug>
事实上,当使用 node debug debug.js 命令调试时,只不过是用 Node.js 命令行工
具将以上两步工作自动完成而已。


11. 大部分基于 Node.js 的应用都是运行在浏览器中的,例如强大的调试工具 node-inspector。
node-inspector 是一个完全基于 Node.js 的开源在线调试工具,提供了强大的调试功能和友好
的用户界面,它的使用方法十分简便。
首先,使用 npm install -g node-inspector 命令安装 node-inspector,然后在终
端中通过 node --debug-brk=5858 debug.js 命令连接你要除错的脚本的调试服务器,
启动 node-inspector:
$ node-inspector
在浏览器中打开 http://127.0.0.1:8080/debug?port=5858 即可显示出优雅的 Web 调试工
具.


12. JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可
以在程序的任何地方访问,即全局变量。在浏览器 JavaScript 中,通常 window 是全局对象,
而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global
对象的属性。


global 最根本的作用是作为全局变量的宿主。按照 ECMAScript 的定义,满足以下条
件的变量是全局变量:
 在最外层定义的变量;
 全局对象的属性;
 隐式定义的变量(未定义直接赋值的变量)。
当你定义一个全局变量时,这个变量同时也会成为全局对象的属性,反之亦然。需要注
意的是,在 Node.js 中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的,
而模块本身不是最外层上下文。


永远使用 var 定义变量以避免引入全局变量,因为全局变量会污染
命名空间,提高代码的耦合风险。


13. process 是一个全局变量,即 global 对象的属性。它用于描述当前 Node.js 进程状态
的对象,提供了一个与操作系统的简单接口。通常在你写本地命令行程序的时候,少不了要
和它打交道.
process.nextTick(callback)的功能是为事件循环设置一项任务,Node.js 会在
下次事件循环调响应时调用 callback。


Node.js 适合 I/O 密集型的应用,而不是计算密集型的应用,
因为一个 Node.js 进程只有一个线程,因此在任何时刻都只有一个事件在执行。如果这个事
件占用大量的 CPU 时间,执行事件循环中的下一个事件就需要等待很久,因此 Node.js 的一
个编程原则就是尽量缩短每个事件的执行时间。process.nextTick() 提供了一个这样的
工具,可以把复杂的工作拆散,变成一个个较小的事件。


function doSomething(args, callback) {
somethingComplicated(args);
callback();
}
doSomething(function onEnd() {
compute();
});


我们假设 compute() 和 somethingComplicated() 是两个较为耗时的函数,以上
的程序在调用 doSomething() 时会先执行 somethingComplicated(),然后立即调用
回调函数,在 onEnd() 中又会执行 compute()。下面用 process.nextTick() 改写上
面的程序:


function doSomething(args, callback) {
somethingComplicated(args);
process.nextTick(callback);
}
doSomething(function onEnd() {
compute();
});


改写后的程序会把上面耗时的操作拆分为两个事件,减少每个事件的执行时间,提高事
件响应速度。


不要使用setTimeout(fn,0)代替process.nextTick(callback),
前者比后者效率要低得多。


14. util.inherits(constructor, superConstructor)是一个实现对象间原型继承
的函数。JavaScript 的面向对象特性是基于原型的,与常见的基于类的不同。JavaScript 没有
提供对象继承的语言级别特性,而是通过原型复制来实现的.
var util = require('util');
function Base() {
this.name = 'base';
this.base = 1991;
this.sayHello = function() {
console.log('Hello ' + this.name);
};
}
Base.prototype.showName = function() {
console.log(this.name);
};
function Sub() {
this.name = 'sub';
}
util.inherits(Sub, Base);
var objBase = new Base();
objBase.showName();
objBase.sayHello();
console.log(objBase);
var objSub = new Sub();
objSub.showName();
//objSub.sayHello();
console.log(objSub);


我们定义了一个基础对象 Base 和一个继承自 Base 的 Sub,Base 有三个在构造函数
内定义的属性和一个原型中定义的函数,通过 util.inherits 实现继承。运行结果如下:
base
Hello base
{ name: 'base', base: 1991, sayHello: [Function] }
sub
{ name: 'sub' }
注意,Sub 仅仅继承了 Base 在原型中定义的函数,而构造函数内部创造的 base 属
性和 sayHello 函数都没有被 Sub 继承。同时,在原型中定义的属性不会被 console.log 作
为对象的属性输出。


15. util.inspect(object,[showHidden],[depth],[colors])是一个将任意对象转换
为字符串的方法,通常用于调试和错误输出。它至少接受一个参数 object,即要转换的对象。
特别要指出的是,util.inspect 并不会简单地直接把对象转换为字符串,即使该对
象定义了 toString 方法也不会调用。


16. events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就
是事件发射与事件监听器功能的封装。EventEmitter 的每个事件由一个事件名和若干个参
数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter 支持
若干个事件监听器。当事件发射时,注册到这个事件的事件监听器被依次调用,事件参数作
为回调函数参数传递。


 EventEmitter.on(event, listener) 为指定事件注册一个监听器,接受一个字
符串 event 和一个回调函数 listener。
 EventEmitter.emit(event, [arg1], [arg2], [...]) 发射 event 事件,传
递若干可选参数到事件监听器的参数表。
 EventEmitter.once(event, listener) 为指定事件注册一个单次监听器,即
监听器最多只会触发一次,触发后立刻解除该监听器。
 EventEmitter.removeListener(event, listener) 移除指定事件的某个监听
器,listener 必须是该事件已经注册过的监听器。
   EventEmitter.removeAllListeners([event]) 移除所有事件的所有监听器,
如果指定 event,则移除指定事件的所有监听器。


17. EventEmitter 定义了一个特殊的事件 error,它包含了“错误”的语义,我们在遇到
异常的时候通常会发射 error 事件。当 error 被发射时,EventEmitter 规定如果没有响
应的监听器,Node.js 会把它当作异常,退出程序并打印调用栈。我们一般要为会发射 error
事件的对象设置监听器,避免遇到错误后整个程序崩溃。


18. 大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、
http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。
为什么要这样做呢?原因有两点。首先,具有某个实体功能的对象实现事件符合语义,
事件的监听和发射应该是一个对象的方法。其次 JavaScript 的对象机制是基于原型的,支持
部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。


19. fs
更名 fs.rename(path1, path2, [callback(err)])
读取文件(文件描述符) fs.read(fd,buffer,offset,length,position,[callback(err,
 bytesRead, buffer)])


20. http.Server 是一个基于事件的 HTTP 服务器,所有的请求都被封装为独立的事件,
开发者只需要对它的事件编写响应函数即可实现 HTTP 服务器的所有功能。它继承自
EventEmitter,提供了以下几个事件。
request:当客户端请求到来时,该事件被触发,提供两个参数 req 和res,分别是
http.ServerRequest 和 http.ServerResponse 的实例,表示请求和响应信息。
connection:当 TCP 连接建立时,该事件被触发,提供一个参数 socket,为
net.Socket 的实例。connection 事件的粒度要大于 request,因为客户端在
Keep-Alive 模式下可能会在同一个连接内发送多次请求。
close :当服务器关闭时,该事件被触发。注意不是在用户连接断开时。
除此之外还有 checkContinue、upgrade、clientError 事件,通常我们不需要关
心,只有在实现复杂的 HTTP 服务器的时候才会用到。
在这些事件中, 最常用的就是 request 了, 因此 http 提供了一个捷径:
http.createServer([requestListener]) , 功能是创建一个 HTTP 服务器并将
requestListener 作为 request 事件的监听函数.


21. response.end([data], [encoding]):结束响应,告知客户端所有发送已经完
成。当所有要返回的内容发送完毕的时候,该函数 必须 被调用一次。它接受两个可
选参数,意义和 response.write 相同。如果不调用该函数,客户端将永远处于
等待状态。


22. http.ClientRequest 像 http.ServerResponse 一样也提供了 write 和 end 函
数,用于向服务器发送请求体,通常用于 POST、PUT 等操作.
http.ClientResponse 与 http.ServerRequest 相似,提供了三个事件 data、end
和 close,分别在数据到达、传输结束和连接结束时触发,其中 data 事件传递一个参数
chunk,表示接收到的数据。


23. Express 在初始化一个项目的时候需要指定模板引擎,默认支持Jade和ejs,为了降低学
习难度我们推荐使用 ejs ①,同时暂时不添加 CSS 引擎和会话支持。


ejs (Embedded JavaScript) 是一个标签替换引擎,其语法与 ASP、PHP 相似,易于学习,目前被广泛应用。Express
默认提供的引擎是 jade,它颠覆了传统的模板引擎,制定了一套完整的语法用来生成 HTML 的每个标签结构,功
能强大但不易学习。


24. 上面的例子是为固定的路径设置路由规则,Express 还支持更高级的路径匹配模式。例
如我们想要展示一个用户的个人页面,路径为 /user/[username],可以用下面的方法定义路由
规则:
app.get('/user/:username', function(req, res) {
res.send('user: ' + req.params.username);
});
修改以后重启 app.js,访问 http://localhost:3000/user/byvoid,可以看到页面显示了以下
内容:
user: byvoid
路径规则 /user/:username 会被自动编译为正则表达式,类似于 \/user\/([^\/]+)\/?
这样的形式。路径参数可以在响应函数中通过 req.params 的属性访问。
路径规则同样支持 JavaScript 正则表达式,例如 app.get(\/user\/([^\/]+)\/?,
callback)。这样的好处在于可以定义更加复杂的路径规则,而不同之处是匹配的参数是匿
名的,因此需要通过 req.params[0]、req.params[1] 这样的形式访问。


25. 。REST的意思是 表征状态转移(Representational State Transfer),它是一种基于 HTTP 协议的网络应
用的接口风格,充分利用 HTTP 的方法实现统一风格接口的服务。HTTP 协议定义了以下8
种标准的方法。
 GET:请求获取指定资源。
 HEAD:请求指定资源的响应头。
 POST:向指定资源提交数据。
 PUT:请求服务器存储一个资源。
 DELETE:请求服务器删除指定资源。
 TRACE:回显服务器收到的请求,主要用于测试或诊断。
 CONNECT:HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
 OPTIONS:返回服务器支持的HTTP请求方法。
其中我们经常用到的是 GET、POST、PUT 和 DELETE 方法。根据 REST 设计模式,这
4种方法通常分别用于实现以下功能。
 GET:获取
 POST:新增
 PUT:更新
 DELETE:删除


Express 支持的 HTTP 请求的绑定函数
请求方式 绑定函数
GET app.get(path, callback)
POST app.post(path, callback)
PUT app.put(path, callback)
DELETE app.delete(path, callback)
PATCH app.patch(path, callback)
TRACE app.trace(path, callback)
CONNECT app.connect(path, callback)
OPTIONS app.options(path, callback)
所有方法 app.all(path, callback)
需要注意的是 app.all 函数,它支持把所有的请求方式绑定到同一个响应函数,是
一个非常灵活的函数,在后面我们可以看到许多功能都可以通过它来实现。


26. 原因是 Express 在处理路由规则时,会优先匹配先定义的路
由规则,因此后面相同的规则被屏蔽.


27. 使用 next() 转移控制权,又被第二条规则捕获,向浏览器
返回了信息。
这是一个非常有用的工具,可以让我们轻易地实现中间件,而且还能提高代码的复用程
度。例如我们针对一个用户查询信息和修改信息的操作,分别对应了 GET 和 PUT 操作,而
两者共有的一个步骤是检查用户名是否合法,因此可以通过 next() 方法实现:
var users = {
'byvoid': {
name: 'Carbo',
website: 'http://www.byvoid.com'
}
};
app.all('/user/:username', function(req, res, next) {
// 检查用户是否存在
if (users[req.params.username]) {
next();
} else {
next(new Error(req.params.username + ' does not exist.'));
}
});
app.get('/user/:username', function(req, res) {
// 用户一定存在,直接展示
res.send(JSON.stringify(users[req.params.username]));
});
app.put('/user/:username', function(req, res) {
// 修改用户信息
res.send('Done');
});
上面例子中,app.all 定义的这个路由规则实际上起到了中间件的作用,把相似请求
的相同部分提取出来,有利于代码维护其他next方法如果接受了参数,即代表发生了错误。
使用这种方法可以把错误检查分段化,降低代码耦合度。


28. 模板引擎的功能是将页面模板和要显示的数据结合起来生成 HTML 页面。它既可以运
行在服务器端又可以运行在客户端,大多数时候它都在服务器端直接被解析为 HTML,解析
完成后再传输给客户端,因此客户端甚至无法判断页面是否是模板引擎生成的。
目前的主流还是由服务器运行模板引擎。


29. 我们在 app.js 中通过以下两个语句设置了模板引擎和页面模板的位置:
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
表明要使用的模板引擎是 ejs,页面模板在 views 子目录下。在 routes/index.js 的
exports.index 函数中通过如下语句调用模板引擎:
res.render('index', { title: 'Express' });
res.render 的功能是调用模板引擎,并将其产生的页面直接返回给客户端。它接受
两个参数,第一个是模板的名称,即 views 目录下的模板文件名,不包含文件的扩展名;第
二个参数是传递给模板的数据,用于模板翻译.


30. ejs 的标签系统非常简单,它只有以下3种标签。
<% code %>:JavaScript 代码。
<%= code %>:显示替换过 HTML 特殊字符的内容。
<%- code %>:显示原始 HTML 内容。
我们可以用它们实现页面模板系统能实现的任何内容。


31. 上面的例子介绍了页面模板的翻译,但我们看到的不止这两行,原因是 Express 还自动
套用了 layout.ejs,它的内容是:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<%- body %>
</body>
</html>
layout.ejs 是一个页面布局模板,它描述了整个页面的框架结构,默认情况下每个单独的
页面都继承自这个框架,替换掉 <%- body %> 部分。这个功能通常非常有用,因为一般为
了保持整个网站的一致风格,HTML 页面的<head>部分以及页眉页脚中的大量内容是重复
的,因此我们可以把它们放在 layout.ejs 中。当然,这个功能并不是强制的,如果想关闭它,
可以在 app.js 的中 app.configure 中添加以下内容,这样页面布局功能就被关闭了。
app.set('view options', {
layout: false
});


另一种情况是,一个网站可能需要不止一种页面布局,例如网站分前台展示和后台管理
系统,两者的页面结构有很大的区别,一套页面布局不能满足需求。这时我们可以在页面模
板翻译时指定页面布局,即设置 layout 属性,例如:
function(req, res) {
res.render('userlist', {
title: '用户列表后台管理系统',
layout: 'admin'
});
};
这段代码会在翻译 userlist 页面模板时套用 admin.ejs 作为页面布局。


32. Express 提供了一种叫做视图助手的工具,它的功能是允许在视图中访问一个全局的函数
或对象,不用每次调用视图解析的时候单独传入。前面提到的 partial 就是一个视图助手。
视图助手有两类,分别是静态视图助手和动态视图助手。这两者的差别在于,静态视图
助手可以是任何类型的对象,包括接受任意参数的函数,但访问到的对象必须是与用户请求无
关的,而动态视图助手只能是一个函数,这个函数不能接受参数,但可以访问 req 和 res 对象。


静态视图助手可以通过 app.helpers() 函数注册,它接受一个对象,对象的每个属性名
称为视图助手的名称,属性值对应视图助手的值。动态视图助手则通过 app.dynamicHelpers()
注册,方法与静态视图助手相同,但每个属性的值必须为一个函数,该函数提供 req 和 res.


视图助手的本质其实就是给所有视图注册了全局变量,因此无需每次在调用模板引擎时
传递数据对象。当我们在后面使用 session 时会发现它是非常有用的。


33. module.exports = {
cookieSecret: 'microblogbyvoid',
db: 'microblog',
host: 'localhost',
};
其中,db 是数据库的名称,host 是数据库的地址。cookieSecret 用于 Cookie 加密与数
据库无关.


34. var settings = require('../settings');
var Db = require('mongodb').Db;
var Connection = require('mongodb').Connection;
var Server = require('mongodb').Server;
module.exports = new Db(settings.db, new Server(settings.host, Connection.DEFAULT_
PORT, {}));


35. 会话是一个比连接粒度更大的概念.一次会话可能包含多次连接,每次连接都被认为是会话的一次操作。
许多应用层网络协议都是由会话支持的,如 FTP、Telnet 等,而 HTTP 协议是无状态的,本身不支持会话.


36. 'connect-mongo' : 
MongoDB session store for Connect and Express
In many circumstances, connect-mongo will not be the only part of your application
 which need a connection to a MongoDB database. It could be interesting to re-use 
 an existing connection.
Alternatively, you can configure connect-mongo to establish a new connection.
just like the jdbc.


37. req.flash 是 Express 提供的一个奇妙的工具,通过它保存的变量只会在用户当前
和下一次的请求中被访问,之后会被清除,通过它我们可以很方便地实现页面的通知
和错误信息显示功能。
res.redirect 是重定向功能,通过它会向用户返回一个 303 See Other 状态,通知
浏览器转向相应页面.
crypto 是 Node.js 的一个核心模块,功能是加密并生成各种散列,使用它之前首先
要声明 var crypto = require('crypto')。我们代码中使用它计算了密码的散
列值.
通过 req.session.user = newUser 向会话对象写入了当前用户的信息,在后面
我们会通过它判断用户是否已经登录.


38. 登入和登出仅仅是 req.session.user 变量的标记,非常简
单。但这会不会有安全性问题呢?不会的,因为这个变量只有服务端才能访问到,只要不是
黑客攻破了整个服务器,无法从外部改动.


39. DRY (Don’t Repeat Yourself) 是软件工程设计的一个基本原则,又称“一次且仅一次”(Once And Only Once),
指的是开发中应该避免相同意义的代码重复出现。


40. 通过调用 next() 转移控制权,这种方法叫做路由中间件.


41. Node.js 的模块可以分为两大类,一类是核心模块,另一类是文件模块。核心模块就是
Node.js 标准 API 中提供的模块,如 fs、http、net、vm 等,这些都是由 Node.js 官方提供
的模块,编译成了二进制代码。我们可以直接通过 require 获取核心模块,例如
require('fs')。核心模块拥有最高的加载优先级,换言之如果有模块与其命名冲突,
Node.js 总是会加载核心模块。


42. 文件模块则是存储为单独的文件(或文件夹)的模块,可能是 JavaScript 代码、JSON 或
编译好的 C/C++ 代码。文件模块的加载方法相对复杂,但十分灵活,尤其是和 npm 结合使
用时。在不显式指定文件模块扩展名的时候,Node.js 会分别试图加上 .js、.json 和 .node扩展
名。.js 是 JavaScript 代码,.json 是 JSON 格式的文本,.node 是编译好的 C/C++ 代码。


43. 文件模块的加载有两种方式,一种是按路径加载,一种是查找 node_modules 文件夹。


44. 下面总结一下使用 require(some_module) 时的加载顺序。
(1) 如果some_module 是一个核心模块,直接加载,结束。
(2) 如果some_module以“ / ”、“ ./ ”或“ ../ ”开头,按路径加载 some_module,结束。
(3) 假设当前目录为 current_dir,按路径加载 current_dir/node_modules/some_module。
如果加载成功,结束。
如果加载失败,令current_dir为其父目录。
重复这一过程,直到遇到根目录,抛出异常,结束。


45. Express支持两种运行模式:开发模式和产品模式,前者的目的是利于调试,后者则是利于部署。使用产
品模式运行服务器的方式很简单,只需设置NODE_ENV 环境变量。通过NODE_ENV=production


46. Node.js 提供了一个核心模块:cluster。cluster的功能是生成与当前进程相同的子进程,
并且允许父进程和子进程之间共享端口。Node.js 的另一个核心模块child_process 也提供了
相似的进程生成功能,但最大的区别在于cluster 允许跨进程端口复用,给我们的网络服务器开
发带来了很大的方便.




47. Node.js 提供了一个核心模块:cluster。cluster的功能是生成与当
前进程相同的子进程,并且允许父进程和子进程之间共享端口。Node.js 的另一个核心模块
child_process 也提供了相似的进程生成功能,但最大的区别在于cluster 允许跨进程端
口复用,给我们的网络服务器开发带来了很大的方便。


为了在外部模块调用app.js,首先需要禁止服务器自动启动。修改app.js,在app.listen
(3000); 前后加上判断语句:
if (!module.parent) {
app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port,
app.settings.env);
}

48. 到目前为止,网站都是运行在3000端口下的,也就是说用户必须在网址中加入:3000才
能访问网站。默认的HTTP 端口是80,因此必须监听80端口才能使网址更加简洁。如果整个
服务器只有一个网站,那么只需让app.js 监听80 端口即可。但很多时候一个服务器上运行
着不止一个网站,尤其是还有用其他语言(如PHP)写成的网站,这该怎么办呢?此时虚拟
主机可以粉墨登场了。
虚拟主机,就是让多个网站共享使用同一服务器同一IP地址,通过域名的不同来划分请
求。主流的HTTP服务器都提供了虚拟主机支持,如Nginx、Apache、IIS等。我们以Nginx为
例,介绍如何通过反向代理实现Node.js 虚拟主机。
在Nginx 中设置反向代理和虚拟主机非常简单,下面是配置文件的一个示例:
server {
listen 80;
server_name mysite.com;
location / {
proxy_pass http://localhost:3000;
}
}
反向代理   Nginx


49. Unicode 与国际化
Node.js 不支持完整的Unicode,很多字符无法用string 表示。公平地说这不是Node.js 的
缺陷,而是JavaScript 标准的问题。目前JavaScript 支持的字符集还是双字节的UCS2,即用
两个字节来表示一个Unicode 字符,这样能表示的字符数量是65536。显然,仅仅是汉字就不
止这个数目,很多生僻汉字,以及一些较为罕见语言的文字都无法表示。这其实是一个历史
遗留问题,像2000 年问题(俗称千年虫)一样,都起源于当时人们的主观判断。最早的Unicode
设计者认为65536个字符足以囊括全世界所有的文字了,因此那个时候盲目兼容Unicode 的系
统或平台(如Windows、Java 和JavaScript)在后来都遇到了问题。
Unicode 随后意识到2个字节是不够的,因此推出了UCS4,即用4 个字节来表示一个
Unicode 字符。很多原先用定长编码的UCS2 的系统都升级为了变长编码的UTF-16,因为只
有它向下兼容UCS2。UTF-16 对UCS2 以内的字符采用定长的双字节编码,而对它以外的部分
使用多字节的变长编码。这种方式的好处是在绝大多数情况下它都是定长的编码,有利于提
高运算效率,而且兼容了UCS2,但缺点是它本质还是变长编码,程序中处理多少有些不便。
许多号称支持UTF-16 的平台仍然只支持它的子集UCS2,而不支持它的变长编码部分。
相比之下,UTF-8 完全是变长编码,有利于传输,而UTF-32 或UCS4 则是4 字节的定长编码,
有利于计算。
当下的JavaScript 内部支持的仍是定长的UCS2 而不是变长的UTF-16,因此对于处理
UCS4 的字符它无能为力。所有的JavaScript 引擎都被迫保留了这个缺陷,包括V8 在内,因
此你无法使用Node.js 处理罕见的字符。想用Node.js 实现一个多语言的字典工具?还是算了
吧,除非你放弃使用string 数据类型,把所有的字符当作二进制的Buffer 数据来处理。


50. 作用域(scope)是结构化编程语言中的重要概念,它决定了变量的可见范围和生命周
期,正确使用作用域可以使代码更清晰、易懂。作用域可以减少命名冲突,而且是垃圾回收
的基本单元。和 C、C++、Java 等常见语言不同,JavaScript 的作用域不是以花括号包围的块
级作用域(block scope),这个特性经常被大多数人忽视,因而导致莫名其妙的错误。例如
下面代码,在大多数类 C 的语言中会出现变量未定义的错误,而在 JavaScript 中却完全合法:
if (true) {
var somevar = 'value';
}
console.log(somevar); // 输出 value
这是因为 JavaScript 的作用域完全是由函数来决定的,if、for 语句中的花括号不是独
立的作用域。

51. JavaScript的作用域是通过函数来定义的,在一个函数中定义的变量只对这个函数内部可见,我们称为函
数作用域。在函数中引用一个变量时,JavaScript 会先搜索当前函数作用域,或者称为“局
部作用域”,如果没有找到则搜索其上层作用域,一直到全局作用域
有一点需要注意:函数作用域的嵌套关系是定义时决定的,而不是调用时决定的,也就
是说,JavaScript 的作用域是静态作用域,又叫词法作用域,这是因为作用域的嵌套关系可
以在语法分析时确定,而不必等到运行时确定。

52. 


__dirname#
{String}
The name of the directory that the currently executing script resides in.
Example: running node example.js from /Users/mjr
console.log(__dirname);


__filename#
{String}
The filename of the code being executed. This is the resolved absolute path of this code file.
For a main program this is not necessarily the same filename used in the command line. 
The value inside a module is the path to that module file.
Example: running node example.js from /Users/mjr
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值