Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,是一个可让 JavaScript 运行在服务器端的平台javascript
Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。Node.js 的包管理器 npm,是全球最大的开源库生态系统。css
采用了单线程、异步式I/O、事件驱动式的程序设计模型html
I/O(input/output),即输入/输出端口。每一个设备都会有一个专用的I/O地址,用来处理本身的输入输出信息。java
ctrl + c - 退出当前终端。node
ctrl + c 按下两次 - 退出 Node REPL。jquery
ctrl + d - 退出 Node REPL.git
向上/向下 键 - 查看输入的历史命令github
tab 键 - 列出当前命令web
help - 列出使用命令数据库
break - 退出多行表达式
.clear - 退出多行表达式
save filename - 保存当前的 Node REPL 会话到指定文件
load filename - 载入当前 Node REPL 会话的文件内容。
Node.js 事件循环
Node.js 是单进程单线程应用程序,可是经过事件和回调支持并发,因此性能很是高。
Node.js 的每个 API 都是异步的,并做为一个独立线程运行,使用异步函数调用,并处理并发。
Node.js 基本上全部的事件机制都是用设计模式中观察者模式实现。
Node.js 单线程相似进入一个while(true)的事件循环,直到没有事件观察者退出,每一个异步事件都生成一个事件观察者,若是有事件发生就调用该回调函数.
观察者模式:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知全部观察者对象,使它们可以自动更新本身。
Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭而后进行处理,而后去服务下一个web请求。
当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。
这个模型很是高效可扩展性很是强,由于webserver一直接受请求而不等待任何读写操做。(这也被称之为非阻塞式IO或者事件驱动IO)
在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。
整个事件驱动的流程就是这么实现的,很是简洁。有点相似于观察者模式,事件至关于一个主题(Subject),而全部注册到这个事件上的处理函数至关于观察者(Observer)。
Node.js 有多个内置的事件,咱们能够经过引入 events 模块,并经过实例化 EventEmitter 类来绑定和监听事件
Node.js 中所谓的 JavaScript 只是 Core JavaScript,或者说是 ECMAScript 的一个实现,不包含 DOM(文档对象模型)、BOM(浏览器对象模型) 或者 Client JavaScript(客户端脚本)。这是由于 Node.js 不运行在浏览器中,因此不须要使用浏览器中的许多特性。
Node.js 能作什么
具备复杂逻辑的网站;
基于社交网络的大规模 Web 应用;
Web Socket 服务器;
TCP/UDP 套接字应用程序;
命令行工具;
交互式终端程序;
带有图形用户界面的本地应用程序;
单元测试工具;
客户端 JavaScript 编译器。
TCP/UDP协议
TCP (Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议。其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操做和多路复用。经过面向链接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出链接好的通道,而后再进行数据发送;而UDP则不为IP提供可靠性、流控或差错恢复功能。通常来讲,TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。TCP支持的应用协议主要有:Telnet、FTP、SMTP等;UDP支持的应用层协议主要有:NFS(网络文件系统)、SNMP(简单网络管理协议)、DNS(主域名称系统)、TFTP(通用文件传输协议)等。
Node.js 用异步式 I/O 和事件驱动代替多线程,带来了可观的性能提高。Node.js 除了使用 V8 做为JavaScript引擎之外,还使用了高效的 libev 和 libeio 库支持事件驱动和异步式 I/O。
CommonJS 为了统一javascript在浏览器外的实现
CommonJS 规范包括了模块(modules)、包(packages)、系统(system)、二进制(binary)、控制台(console)、编码(encodings)、文件系统(filesystems)、套接字(sockets)、单元测试(unit testing)等部分。目前大部分标准都在拟定和讨论之中,已经发布的标准有
Modules/1.0、Modules/1.一、Modules/1.1.一、Packages/1.0、System/1.0。
REPL (Read-eval-print loop),即输入—求值—输出循环
例子:
$ node
> console.log('Hello World');
Hello World
Undefined
Supervisor工具
在开发 Node.js 实现的 HTTP 应用时会发现,不管你修改了代码的哪一部份,都必须终止
Node.js 再从新运行才会奏效。这是由于 Node.js 只有在第一次引用到某部份时才会去解析脚本文件,之后都会直接访问内存,避免重复载入,
Node.js的这种设计虽然有利于提升性能,却不利于开发调试,因
为咱们在开发过程当中老是但愿修改后当即看到效果,而不是每次都要终止进程并重启。
supervisor 能够帮助你实现这个功能,它会监视你对代码的改动,并自动重启 Node.js。
使用方法很简单,首先使用 npm 安装 supervisor:
$ npm install -g supervisor
若是你使用的是 Linux 或 Mac,直接键入上面的命令极可能会有权限错误。缘由是 npm须要把 supervisor 安装到系统目录,须要管理员受权,可使用 sudo npm install -gsupervisor 命令来安装。
接下来,使用 supervisor 命令启动 app.js:
$ supervisor app.js
异步式 I/O 与事件式编程
Node.js 最大的特色就是异步式 I/O(或者非阻塞 I/O)与事件紧密结合的编程模式。这种模式与传统的同步式 I/O 线性的编程思路有很大的不一样,由于控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元。
阻塞与线程
什么是阻塞(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 的缓存被清空,切换回来的时候还要从新从内存中读取信息,破坏了数据的局部性。
回调函数
Node.js 中如何用异步的方式读取一个文件
//readfile.js
var fs = require('fs');
fs.readFile('file.txt', 'utf-8', function(err, data) {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
console.log('end.');
运行的结果以下:
end.
Contents of the file.
注:function()是回调函数,在Node.js 中,异步式 I/O 是经过回调函数来实现的。 fs.readFile 接收了三个参数,第一个是文件名,第二个是编码方式,第三个是一个函数,咱们称这个函数为回调函数。
JavaScript 支持匿名的函数定义方式,譬如咱们例子中回调函数的定义就是嵌套在fs.readFile 的参数表中的。这种定义方式在 JavaScript 程序中极为广泛,与下面这种定义
方式实现的功能是一致的:
//readfilecallback.js
function readFileCallBack(err, data) {
if (err) {
console.error(err);
} else {
console.log(data);
}
}
var fs = require('fs');
fs.readFile('file.txt', 'utf-8', readFileCallBack);
console.log('end.');
fs.readFile 调用时所作的工做只是将异步式 I/O 请求发送给了操做系统,而后当即返回并执行后面的语句,执行完之后进入事件循环监听事件。当 fs 接收到 I/O 请求完成的事件时,事件循环会主动调用回调函数以完成后续工做。所以咱们会先看到 end. ,再看到file.txt 文件的内容
事件
Node.js 全部的异步 I/O 操做在完成时都会发送一个事件到事件队列。在开发者看来,事件由 EventEmitter 对象提供。
Node.js 的事件循环机制
Node.js 在何时会进入事件循环呢?答案是 Node.js 程序由事件循环开始,到事件循环结束,全部的逻辑都是事件的回调函数,因此 Node.js 始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。事件的回调函数在执行的过程当中,可能会发出 I/O 请求或直接发射(emit)事件,执行完毕后再返回事件循环,事件循环会检查事件队列中有没有未处理的事件,直到程序结束。
模块和包
什么是模块
模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个Node.js 文件就是一个模块,这个文件多是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。
在前面章节的例子中,咱们曾经用到了 var http = require('http'), 其中 http是 Node.js 的一个核心模块,其内部是用 C++ 实现的,外部用 JavaScript 封装。咱们经过require 函数获取了这个模块,而后才能使用其中的对象。
建立及加载模块
1. 建立模块
一个文件就是一个模块
Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口, require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。
例子:
//module.js
var name;
exports.setName = function(thyName) {
name = thyName;
};
exports.sayHello = function() {
console.log('Hello ' + name);
};
在同一目录下建立 getmodule.js,内容是:
//getmodule.js
var myModule = require('./module');
myModule.setName('BYVoid');
myModule.sayHello();
运行node getmodule.js,结果是:
Hello BYVoid
在以上示例中,module.js 经过 exports 对象把 setName 和 sayHello 做为模块的访问接口,在 getmodule.js 中经过 require('./module') 加载这个模块,而后就能够直接访问 module.js 中 exports 对象的成员函数了。
2. 单次加载
上面这个例子有点相似于建立一个对象,但实际上和对象又有本质的区别,由于
require 不会重复加载模块,也就是说不管调用多少次 require, 得到的模块都是同一个。
咱们在 getmodule.js 的基础上稍做修改:
//loadmodule.js
var hello1 = require('./module');
hello1.setName('BYVoid');
var hello2 = require('./module');
hello2.setName('BYVoid 2');
hello1.sayHello();
运行后发现输出结果是 Hello BYVoid 2 ,这是由于变量 hello1 和hello2 指向的是同一个实例,所以 hello1.setName 的结果被 hello2.setName 覆盖,最终输出结果是由后者决定的。
3. 覆盖 exports
把一个对象封装到模块中,例如:
//singleobject.js
function Hello() {
var name;
this.setName = function (thyName) {
name = thyName;
};
this.sayHello = function () {
console.log('Hello ' + name);
};
};
exports.Hello = Hello;
此时咱们在其余文件中须要经过 require('./singleobject').Hello 来获取Hello 对象,这略显冗余,能够用下面方法稍微简化:
//hello.js
function Hello() {
var name;
this.setName = function (thyName) {
name = thyName;
};
this.sayHello = function () {
console.log('Hello ' + name);
};
};
module.exports = Hello;
//gethello.js
var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello()
注意,模块接口的惟一变化是使用 module.exports = Hello 代替了 exports.Hello=Hello 。在外部引用该模块时,其接口对象就是要输出的 Hello 对象自己,而不是原先的exports 。
事实上, exports 自己仅仅是一个普通的空对象,即 {} ,它专门用来声明接口,本质上是经过它为模块闭包的内部创建了一个有限的访问接口。由于它没有任何特殊的地方,因此能够用其余东西来代替,譬如咱们上面例子中的 Hello 对象。
警告:
不能够经过对exports 直接赋值代替对module.exports 赋值。
exports 实际上只是一个和 module.exports 指向同一个对象的变量,它自己会在模块执行结束后释放,但 module不会,所以只能经过指定module.exports 来改变访问接口。
建立包
包是在模块基础上更深一步的抽象,Node.js 的包相似于 C/C++ 的函数库或者 Java/.Net的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Node.js 根据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求。
Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件 package.json。严格符合 CommonJS 规范的包应该具有如下特征:
package.json 必须在包的顶层目录下;
二进制文件应该在 bin 目录下;
JavaScript 代码应该在 lib 目录下;
文档应该在 doc 目录下;
单元测试应该在 test 目录下。
1. 做为文件夹的模块
模块与文件是一一对应的。文件不只能够是 JavaScript 代码或二进制代码,还能够是一个文件夹。最简单的包,就是一个做为文件夹的模块。
例子:
创建一个叫作 somepackage 的文件夹,在其中建立 index.js,内容以下:
//somepackage/index.js
exports.hello = function() {
console.log('Hello.');
};
而后在 somepackage 以外创建 getpackage.js,内容以下:
//getpackage.js
var somePackage = require('./somepackage');
somePackage.hello();
运行 node getpackage.js,控制台将输出结果 Hello. 。
咱们使用这种方法能够把文件夹封装为一个模块,即所谓的包。包一般是一些模块的集合,在模块的基础上提供了更高层的抽象,至关于提供了一些固定接口的函数库。经过定制
package.json,咱们能够建立更复杂、更完善、更符合规范的包用于发布。
在前面例子中的 somepackage 文件夹下,咱们建立一个叫作 package.json 的文件,内容如
下所示:
{
"main" : "./lib/interface.js"
}
而后将 index.js 重命名为 interface.js 并放入 lib 子文件夹下。以一样的方式再次调用这个包,依然能够正常使用。
Node.js 在调用某个包时,会首先检查包中 package.json 文件的 main 字段,将其做为包的接口模块,若是 package.json 或 main 字段不存在,会尝试寻找 index.js 或 index.node 做为包的接口。
备注:package.json里面不能有main{}之类的注释,否则会报错
package.json是CommonJS 规定的用来描述包的文件,彻底符合规范的 package.json 文件应该含有如下字段。
name :包的名称,必须是惟一的,由小写英文字母、数字和下划线组成,不能包含空格。
description :包的简要说明。
version :符合语义化版本识别
keywords :关键字数组,一般用于搜索。
maintainers :维护者数组,每一个元素要包含 name 、 email (可选)、 web (可选)字段。
contributors :贡献者数组,格式与 maintainers 相同。包的做者应该是贡献者数组的第一个元素。
bugs :提交bug的地址,能够是网址或者电子邮件地址。
licenses :许可证数组,每一个元素要包含 type (许可证的名称)和 url (连接到许可证文本的地址)字段。
repositories :仓库托管地址数组,每一个元素要包含 type (仓库的类型,如 git )、url (仓库的地址)和 path (相对于仓库的路径,可选)字段。
dependencies :包的依赖,一个关联数组,由包名称和版本号组成。
例子:
{
"name": "mypackage",
"description": "Sample package for CommonJS. This package demonstrates the required
elements of a CommonJS package.",
"version": "0.7.0",
"keywords": [
"package",
"example"
],
"maintainers": [
{
"name": "Bill Smith",
"email": "bills@example.com",
}
],
"contributors": [
{
"name": "BYVoid",
"web": "http://www.byvoid.com/"
}
],
"bugs": {
"mail": "dev@example.com",
"web": "http://www.example.com/bugs"
},
"licenses": [
{
"type": "GPLv2",
"url": "http://www.example.org/licenses/gpl.html"
}
],
"repositories": [
{
"type": "git",
"url": "http://github.com/BYVoid/mypackage.git"
}
],
"dependencies": {
"webkit": "1.2",
"ssl": {
"gnutls": ["1.0", "2.0"],
"openssl": "0.9.8"
}
}
}
Node.js 包管理器
Node.js包管理器,即npm是 Node.js 官方提供的包管理工具,它已经成了 Node.js 包的标准发布平台,用于 Node.js 包的发布、传播、依赖控制。npm 提供了命令行工具,使你能够方便地下载、安装、升级、删除包,也可让你做为开发者发布并维护包。
本地模式和全局模式
本地模式:npm [install/i] [package_name]
全局模式:npm [install/i] -g [package_name]
为何要使用全局模式呢?多数时候并非由于许多程序都有可能用到它,为了减小多重副本而使用全局模式,而是由于本地模式不会注册 PATH 环境变量。举例说明,咱们安装supervisor 是为了在命令行中运行它,譬如直接运行 supervisor script.js,这时就须要在 PATH环境变量中注册 supervisor。npm 本地模式仅仅是把包安装到 node_modules 子目录下,其中的 bin 目录没有包含在 PATH 环境变量中,不能直接在命令行中调用。而当咱们使用全局模式安装时,npm 会将包安装到系统目录,譬如 /usr/local/lib/node_modules/,同时 package.json 文件中 bin 字段包含的文件会被连接到 /usr/local/bin/。/usr/local/bin/ 是在 PATH 环境变量中默认定义的,所以就能够直接在命令行中运行 supervisor script.js 命令了。
建立全局连接
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 子目录中发现一个指向安装到全局的包的符号连接。经过这种方法,咱们就能够把全局包当本地包来使用了。
包的发布
*编写模块
1)新建文件夹,好比:somepackage
2) 该文件夹下新建js文件,好比:index.js
js内容以下:
exports.sayHello=function(){
return "Hello,zhoudaozhang.";
};
*初始化包描述文件
使用cmd命令定位到somepackage文件夹
输入 npm init 并执行
npm的init命令能够帮助你生成package.json文件,这是个人文件内容:
{
"name": "somepackage_xiaotian",
"version": "1.0.0",
"description": "'hehe'",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Hello",
"world"
],
"author": "zhou daozhang",
"license": "ISC"
}
*注册包仓库帐号
npm adduser
输入这个命令会有提示输入用户名,密码,邮箱等资料
这和去官方源仓库https://www.npmjs.com/注册是同样的
*上传包
npm publish
若是上传成功会提示
+somepackage_xiaotian@1.0.0 不然上传失败
这个时候去https://www.npmjs.com/登录仓库帐号就能够看到本身的包啦
*安装包
npm install somepackage_xiaotian
经过此命令能够在世界上任一一台机器上安装somepackage_xiaotian了
发布包过程可能会遇到不少问题,我印象比较深入的是npm ERR publish 403
You do not have permission to publish 'somepackage'.Are you logged in as
the corrent user?:somepackage
意思是我没权限发布somepackage,并问我是否使用了正确的帐号,
那也许是somepackage被别人发布过了吧,因此我修改了package.json文件
把name改为somepackage_xiaotian.
*分析包
这个命令能够为你分析出当前路径下可以经过模块路径找到的全部包,并生成依赖树。
npm ls
全局对象Global
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其全部属性均可以在程序的任何地方访问,即全局变量。在浏览器 JavaScript 中,一般 window 是全局对象,而 Node.js 中的全局对象是 global ,全部全局变量(除了 global 自己之外)都是 global
对象的属性。
全局对象与全局变量
global 最根本的做用是做为全局变量的宿主
知足如下条件的变量是全局变量:
在最外层定义的变量;
全局对象的属性;
隐式定义的变量(未定义直接赋值的变量)。
注:在 Node.js 中你不可能在最外层定义变量,由于全部用户代码都是属于当前模块的,而模块自己不是最外层上下文
提示:永远使用 var 定义变量以免引入全局变量,由于全局变量会污染命名空间,提升代码的耦合风险。
Process
process 是一个全局变量,即 global 对象的属性。它用于描述当前 Node.js 进程状态的对象,提供了一个与操做系统的简单接口
process.argv 是命令行参数数组,第一个元素是 node,第二个元素是脚本文件名
process.stdout 是标准输出流,一般咱们使用的 console.log() 向标准输出打印字符,而 process.stdout.write() 函数提供了更底层的接口。
process.stdin 是标准输入流,初始时它是被暂停的,要想从标准输入读取数据,你必须恢复流,并手动编写流的事件响应函数。
process.nextTick(callback) 的功能是为事件循环设置一项任务,Node.js 会在下次事件循环调响应时调用 callback 。
http://nodejs.org/api/process.html详细了解的地方
console
console 用于提供控制台标准输出
console.log() :向标准输出流打印字符并以换行符结束
console.error() :与 console.log() 用法相同,只是向标准错误流输出。
console.trace() :向标准错误流输出当前的调用栈。
经常使用工具 util
util 是一个 Node.js 核心模块,提供经常使用函数的集合,用于弥补核心 JavaScript 的功能过于精简的不足。
util.inherits
util.inherits(constructor, superConstructor) 是一个实现对象间原型继承的函数。
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
Hello base
{ name: 'base', base: 1991, sayHello: [Function] }
sub
{ name: 'sub' }
这个只能继承原型里面的属性和方法
util.inspect
util.inspect(object,[showHidden],[depth],[colors]) 是一个将任意对象转换为字符串的方法,一般用于调试和错误输出。它至少接受一个参数 object ,即要转换的对象。
showHidden 是一个可选参数,若是值为 true ,将会输出更多隐藏信息。
depth 表示最大递归的层数,若是对象很复杂,你能够指定层数以控制输出信息的多
少。若是不指定 depth ,默认会递归2层,指定为 null 表示将不限递归层数完整遍历对象。
若是 color 值为 true ,输出格式将会以 ANSI 颜色编码,一般用于在终端显示更漂亮的效果。
注:util.inspect 并不会简单地直接把对象转换为字符串,即便该对
象定义了 toString 方法也不会调用。
module.exports 和exports的区别?
Module.exports才是真正的接口,exports只不过是它的一个辅助工具。 最终返回给调用的是Module.exports而不是exports。
全部的exports收集到的属性和方法,都赋值给了Module.exports。固然,这有个前提,就是Module.exports自己不具有任何属性和方法。若是,Module.exports已经具有一些属性和方法,那么exports收集来的信息将被忽略。
这就是 EventEmitter 最简单的用法。接下来咱们介绍一下 EventEmitter 经常使用的API。
EventEmitter.on(event, listener) 为指定事件注册一个监听器,接受一个字符串 event 和一个回调函数 listener 。
EventEmitter.emit(event, [arg1], [arg2], [...]) 发射 event 事件,传递若干可选参数到事件监听器的参数表。
EventEmitter.once(event, listener) 为指定事件注册一个单次监听器,即监听器最多只会触发一次,触发后马上解除该监听器。
EventEmitter.removeListener(event, listener) 移除指定事件的某个监听器, listener 必须是该事件已经注册过的监听器。
文件系统 fs
fs 模块是文件操做的封装,它提供了文件的读取、写入、改名、删除、遍历目录、连接等 POSIX 文件系统操做。与其余模块不一样的是, fs 模块中全部的操做都提供了异步的和同步的两个版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的fs.readFileSync() 。
fs.readFile
fs.readFile(filename,[encoding],[callback(err,data)]) 是最简单的读取
文件的函数。它接受一个必选参数 filename ,表示要读取的文件名。第二个参数 encoding是可选的,表示文件的字符编码。 callback 是回调函数,用于接收文件的内容。若是不指定 encoding ,则 callback 就是第二个参数。回调函数提供两个参数 err 和 data , err 表示有没有错误发生, data 是文件内容。若是指定了 encoding , data 是一个解析后的字符串,不然 data 将会是以 Buffer 形式表示的二进制数据。
例;
以 Buffer 形式表示的二进制数据
var fs = require('fs');
fs.readFile('content.txt', function(err, data) {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
encoding 指定编码
var fs = require('fs');
fs.readFile('content.txt', 'utf-8', function(err, data) {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
fs.readFileSync
fs.readFileSync(filename, [encoding]) 是 fs.readFile 同步的版本。它接受的参数和 fs.readFile 相同,而读取到的文件内容会以函数返回值的形式返回。若是有错误发生, fs 将会抛出异常,你须要使用 try 和 catch 捕捉并处理异常。
Fs.read
fs.read(fd, buffer, offset, length, position, [callback(err, bytesRead,
buffer)]) 是 POSIX read 函数的封装,相比fs.readFile 提供了更底层的接口。 fs.read的功能是从指定的文件描述符fd 中读取数据并写入 buffer 指向的缓冲区对象。offset 是buffer 的写入偏移量。length 是要从文件中读取的字节数。position 是文件读取的起始
位置,若是 position 的值为 null ,则会从当前文件指针的位置读取。回调函数传递bytesRead 和 buffer ,分别表示读取的字节数和缓冲区对象。
用的时候,和fs.open一块儿用
var fs = require('fs');
fs.open('content.txt', 'r', function(err, fd) {
if (err) {
console.error(err);
return;
}
var buf = new Buffer(8);
fs.read(fd, buf, 0, 8, null, function(err, bytesRead, buffer) {
if (err) {
console.error(err);
return;
}
console.log('bytesRead: ' + bytesRead);
console.log(buffer);
})
});
运行结果则是:
bytesRead: 8
<Buffer 54 65 78 74 20 e6 96 87>
HTTP 服务器与客户端
工程的结构
Express 都生成了哪些文件,除了 package.json,它只产生了两个 JavaScript 文件 app.js 和 routes/index.js。模板引擎 ejs 也有两文件 index.ejs 和layout.ejs,此外还有样式表 style.css。下面来详细看看这几个文件。
routes 是一个文件夹形式的本地模块,即 ./routes/index.js ,它的功能是为指定路径组织返回内容,至关于 MVC 架构中的控制器。
routes/index.js 是路由文件,至关于控制器,用于组织展现的内容:
exports.index = function(req, res) {
res.render('index', { title: 'Express' });
};
app.js 中经过 app.get('/', routes.index); 将“ / ”路径映射到 exports.index函数下。其中只有一个语句 res.render('index', { title: 'Express' }) ,功能是调用模板解析引擎,翻译名为 index 的模板,并传入一个对象做为参数,这个对象只有一个
属性,即 title: 'Express' 。
3. index.ejs
index.ejs 是模板文件,即 routes/index.js 中调用的模板,内容是:
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
它的基础是 HTML 语言,其中包含了形如<%= title %>的标签,功能是显示引用的变量,即 res.render 函数第二个参数传入的对象的属性。
4. layout.ejs (新的里面,没有这个)
模板文件不是孤立展现的,默认状况下全部的模板都继承自 layout.ejs,即<%- body %>
部分才是独特的内容,其余部分是共有的,能够看做是页面框架。
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<%- body %>
</body>
</html>
REST 风格的路由规则
表征状态转移(Representational State Transfer),它是一种基于 HTTP 协议的网络应用的接口风格,充分利用 HTTP 的方法实现统一风格接口的服务。
安全是指没有反作用,即请求不会对资源产生变更,连续访问屡次所得到的结果不受访问者的影响。而幂等指的是重复请求屡次与一次请求的效果是同样的,好比获取和更新操做是幂等的,这与新增不一样。删除也是幂等的,即重复删除一个资源,和删除一次是同样的。
在 MVC 架构中,模板引擎包含在服务器端。
基于 JavaScript 的模板引擎有许多种实现,咱们推荐使用 ejs (Embedded JavaScript),由于它十分简单,并且与 Express 集成良好。因为它是标准 JavaScript 实现的,所以它不只能够运行在服务器端,还能够运行在浏览器中。
ejs 的标签系统很是简单,它只有如下3种标签。
<% code %>:JavaScript 代码。
<%= code %>:显示替换过 HTML 特殊字符的内容。
<%- code %>:显示原始 HTML 内容。
视图助手
Express 提供了一种叫作视图助手的工具,它的功能是容许在视图中访问一个全局的函数或对象,不用每次调用视图解析的时候单独传入。前面提到的 partial 就是一个视图助手。
因为ejs的升级,《node.js开发指南》中使用的 partial 函数已经摒弃,使用foreach,include代替
<%- partial('listitem',items) %>
修改成:
1 2 3 4 |
<ul><% items.forEach(function(listitem){%> <% include listitem%> <%}) %> </ul> |
next()
Express 提供了路由控制权转移的方法,即回调函数的第三个参数 next ,经过调用next() ,会将路由控制权转移给后面的规则,例如:
app.all('/user/:username', function(req, res, next) {
console.log('all methods captured');
next();
});
app.get('/user/:username', function(req, res) {
res.send('user: ' + req.params.username);
});
当访问被匹配到的路径时,如 http://localhost:3000/user/carbo,会发现终端中打印了 allmethods captured ,并且浏览器中显示了 user: carbo 。这说明请求先被第一条路由规则捕获,完成 console.log 使用 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 方法若是接受了参数,即表明发生了错误。
使用这种方法能够把错误检查分段化,下降代码耦合度。
微博网站过程当中发现的一些问题;
一、 css和javascritp的路径不用写绝对路径,例:
<script src="/javascripts/jquery-1.11.3.js"></script>
<script src="/javascripts/bootstrap.js"></script>
<link rel='stylesheet' href='/stylesheets/bootstrap.css' />
<link href="/stylesheets/bootstrap-responsive.css" rel="stylesheet">
二、 app.js里面要加这个
三、 var partials = require('express-partials');
app.use(partials());
以前要安装express-partials模块,npm install express-partials –g
3上面这个要写在
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
后面
数据库的格式是由表(table)、行(row)、字段(field)组成的。表有固定的结构,规定了
每行有哪些字段,在建立时被定义,以后修改很困难。行的格式是相同的,由若干个固定的字段组成。每一个表可能有若干个字段做为索引(index),这其中有的是主键(primary key),用于约束表中的数据,还有惟一键(unique key),确保字段中不存放重复数据。表和表之间
可能还有相互的约束,称为外键(foreign key)。对数据库的每次查询都要以行为单位,复杂的查询包括嵌套查询、链接查询和交叉表查询。
拥有这些功能的数据库被称为关系型数据库,关系型数据库一般使用一种叫作 SQL(Structured Query Language)的查询语言做为接口,所以又称为 SQL 数据库。典型的 SQL 数据库有 MySQL、Oracle、Microsoft SQL Server、PostgreSQL、SQLite,等等。
会话
会话是一种持久的网络协议,用于完成服务器和客户端之间的一些交互行为。会话是一个比链接粒度更大的概念,一次会话可能包含屡次链接,每次链接都被认为是会话的一次操做。在网络应用开发中,有
必要实现会话以帮助用户交互。例如网上购物的场景,用户浏览了多个页面,购买了一些物品,这些请求在屡次链接中完成。许多应用层网络协议都是由会话支持的,如 FTP、Telnet 等,而 HTTP 协议是无状态的,自己不支持会话,所以在没有额外手段的帮助下,前面场景中服务器不知道用户购买了什么。
为了在无状态的 HTTP 协议之上实现会话,Cookie 诞生了。Cookie 是一些存储在客户端的信息,每次链接的时候由浏览器向服务器递交,服务器也向浏览器发起存储 Cookie 的请求,依靠这样的手段服务器能够识别客户端。咱们一般意义上的 HTTP 会话功能就是这样
实现的。具体来讲,浏览器首次向服务器发起请求时,服务器生成一个惟一标识符并发送给客户端浏览器,浏览器将这个惟一标识符存储在 Cookie 中,之后每次再发起请求,客户端浏览器都会向服务器传送这个惟一标识符,服务器经过这个惟一标识符来识别用户。
Underscore模块