Node.js概述
Node.js是基于V8, 用于开发网络应用的一个平台, 它主要包含了基于TCP的异步操作和同步文件管理.
为什么要使用Node.js
Node.js使用的是非阻塞的I/O.
Node.js的主要特性
EventEmitter: 事件API
简单来说, 就是事件的发送接收机制. 例如在系统的某个地方, 我们发送了一个start消息, 在另外一个专门等待start信息的模块接收到这个信息后, 就开始处理事情.
Node.js中的streams, networking和file system都基于EventEmitter.
var EventEmitter = require('events').EventEmitter;
class MyEmitter extends EventEmitter {}
var myEmitter = new MyEmitter();
myEmitter.on('show', () => {
console.log('hello world');
});
myEmitter.emit('show');
Stream: 可扩展I/O
Stream是基于EventEmitter, 主要用于处理数据流.
使用Stream可以创建一个对象来接收所连接事件的数据: data用于新数据流入, end表明没有数据流入, 而error表明错误发生.
const fs = require('fs');
var input = fs.createReadStream(__filename);
input.on('data', (chunk) => {
if (chunk) {
process.stdout.write(chunk);
}
});
input.on('end', () => {
process.stdout.write('\nend!\n');
});
input.on('error', (err) => {
if (err) throw err;
});
使用pipe管道可简化代码:
var fs = require('fs');
var input = fs.createReadStream(__filename);
var output = fs.createWriteStream('output.txt');
input.pipe(output);
不使用流处理:
var fs = require('fs');
fs.readFile(__filename, (err, chunk) => {
if (err) return console.error(err);
if (chunk) process.stdout.write(chunk.toString());
});
FS: 文件操作
可同步或异步的读取文件.
NET: 用于创建网络客户端和服务端
基于HTTP协议, 用于操作网络.
Node.js环境
模块
当我们安装第三方模块时, 我们可以使用npm.
lgtdeMacBook-Pro:test lgt$ cnpm install express
→ express@4.14.0 › type-is@1.6.14 (09:11:00)
→ express@4.14.0 › accepts@1.3.3 › mime-types@2.1.13 (05:39:28)
lgtdeMacBook-Pro:test lgt$ vim test.js
lgtdeMacBook-Pro:test lgt$ node test.js
function
lgtdeMacBook-Pro:test lgt$ cat test.js
var express = require('express');
console.log(typeof express);
这里使用了淘宝镜像cnpm, 安装则执行:
cnpm install xxx
默认安装在当前目录的node_modules下. 如果加上 -g 参数, 则默认全局安装, 一般在/usr/local/lib/node_modules下.
使用require("xxx")来加载模块.
通过module.exports加载当个文件的单个对象, 而exports加载单个文件的多个对象.
编程讲究模块思想, 我们可以将代码分割成多个功能模块, 使用module.exports/exports进行加载.
myclass.js:
function MyClass() {}
MyClass.prototype = {
method: function() {
return 'Hello';
}
};
var myClass = new MyClass();
module.exports = myClass;
module-2.js:
exports.method = function() {
return 'Hello';
};
exports.method2 = function() {
return 'Hello again';
};
test.js:
var myClass = require('./myclass');
var module2 = require('./module-2');
console.log(myClass.method());
console.log(module2.method());
console.log(module2.method2());
运行程序, 则输出:
lgtdeMacBook-Pro:test lgt$ node test.js
Hello
Hello
Hello again
因为存在require.cache, 所以不要担心不断的require导致文件被重复的加载. 而require.resolve会解析出当前加载模块的具体文件路径, 如果想卸载某个功能模块, 我们可以编写如下的代码:
delete require.cache(require.resolve('./myclass'));
加载一组文件情况下, 我们可以编写index.js
在group目录下有三个文件:
one.js:
module.exports = function() {
console.log('one');
};
two.js:
module.exports = function() {
console.log('two');
};
index.js:
module.exports = {
one: require('./one'),
two: require('./two')
};
test.js:
var group = require('./group');
group.one();
group.two();
运行程序, 则输出:
lgtdeMacBook-Pro:test lgt$ node test.js
one
two
在require一个目录时候, 默认会加载index.js文件.
使用__dirname/__filename来获取当前目录和文件
lgtdeMacBook-Pro:test lgt$ cat test.js
console.log('__dirname:', __dirname);
console.log('__filename:', __filename);
lgtdeMacBook-Pro:test lgt$ node test.js
__dirname: /Users/lgt/test
__filename: /Users/lgt/test/test.js
I/O
从标准输入输出流(process.stdin/process.stdout)进行数据的读写
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(text) {
process.stdout.write(text.toUpperCase());
});
运行程序, 则输出:
lgtdeMacBook-Pro:test lgt$ cat process.js | node process.js
PROCESS.STDIN.RESUME();
PROCESS.STDIN.SETENCODING('UTF8');
PROCESS.STDIN.ON('DATA', FUNCTION(TEXT) {
PROCESS.STDOUT.WRITE(TEXT.TOUPPERCASE());
});
这里用到了管道, process.stdin为标准输入流, process.stdout为标准输出流.
使用console.time()/console.timeEnd()来统计运行时间
console.time('read');
var fs = require('fs');
var input = fs.createReadStream(__filename);
var output = fs.createWriteStream('output.txt');
input.pipe(output);
input.on('end', () => {
console.timeEnd('read');
});
终端输出:
leicj@leicj:~/test$ node test.js
read: 5.922ms
操作系统和命令行
使用process.arch/process.platform获取操作系统和平台信息
lgtdeMacBook-Pro:test lgt$ node test.js
process.arch: x64
process.platform: darwin
使用process.argv传递命令行参数
lgtdeMacBook-Pro:test lgt$ node test.js a b c d
process.argv: [ '/usr/local/bin/node',
'/Users/lgt/test/test.js','a','b','c','d' ]
使用process.exit(status_code)传递状态码并退出程序
使用process来接收信号
process.stdin.resume();
process.on('SIGINT', () => {
console.log('Reloading configuration...');
});
console.log('PID:', process.pid);
运行终端:
leicj@leicj:~/test$ node test.js
PID: 7203
^CReloading configuration...
^CReloading configuration...
^CReloading configuration...
^CReloading configuration...
^CReloading configuration...
^CReloading configuration...
^CReloading configuration...
Buffers: 认识bits, bytes和encodings
改变数据编码
默认情况下, Node.js的核心功能模块都是返回buffer的.
将buffer转换为其他数据结构
使用Buffer的toString方法, 将buffer转换为默认的UTF-8.
var fs = require('fs');
fs.readFile(__filename, (err, chunk) => {
if (err) return console.error(err);
console.log(chunk);
console.log(chunk.toString());
});
备注: 这里不能使用process.stdout.write, 否则输出字符串. 考虑以下代码:
> var buf = Buffer.from('hello')
undefined
> buf + ''
'hello'
> buf
<Buffer 68 65 6c 6c 6f>
如果在toString()中传递参数, 则将buffer转换为其它数据结构, 也可以通过new Buffer(str, 'xxx')将其它数据结构转换为buffer.
> var s = "hello"
undefined
> var s1 = new Buffer(s).toString('base64')
undefined
> s1
'aGVsbG8='
> new Buffer(s1, 'base64').toString()
'hello'
事件: 处理EventEmitter
基本用法
从EventEmitter中继承
EventEmitter中存在两个基本函数: on用于接收信号, 而emit用于发射信号.
var EventEmitter = require('events').EventEmitter;
class MyEmitter extends EventEmitter {}
var myEmitter = new MyEmitter();
myEmitter.on('show', () => {
console.log('show');
});
myEmitter.on('show', () => {
console.log('show again');
});
myEmitter.emit('show');
终端输出:
lgtdeMacBook-Pro:test lgt$ node test.js
show
show again
EventEmitter提供两个函数用于删除listener, emitter.removeListener用于删除特定的的事件, 而emitter.removeAllListeners用于删除所有的事件.
而对于emitter.removeListener来说, 需要提供第二个参数: 事件中执行的函数名.
var EventEmitter = require('events').EventEmitter;
class MyEmitter extends EventEmitter {}
var myEmitter = new MyEmitter();
function f1() {
console.log('f1');
}
function f2() {
console.log('f2');
}
myEmitter.on('show', f1);
myEmitter.on('show', f2);
myEmitter.removeListener('show', f1);
myEmitter.emit('show');
运行程序, 则输出:
lgtdeMacBook-Pro:test lgt$ node test.js
f2
错误处理
正常情况下, 如果程序发生异常, 则会将异常信息打印在终端, 并且终止程序.
但我们可以捕获异常信息:
var EventEmitter = require('events').EventEmitter;
class MyEmitter extends EventEmitter {}
var myEmitter = new MyEmitter();
myEmitter.on('show', () => {
myEmitter.emit('error', 'unable to show');
});
myEmitter.on('error', (err) => {
console.error('Error:', err);
});
myEmitter.emit('show');
运行程序, 输出:
lgtdeMacBook-Pro:test lgt$ node test.js
Error: unable to show
如果存在多个EventEmitter对象, 甚至存在嵌套的对象, 每个对象都会emit一个错误信息, 则最好使用domain来处理异常.
var d = require('domain').create();
d.on('error', (er) => {
console.log('error, but oh well', er.message);
});
d.run(() => {
require('http').createServer((req, res) => {
handleRequest(req, res);
}).listen(PORT);
});
这时候, 如果运行程序, 则提示如下的错误:
error, but oh well PORT is not defined
高级主题
假设存在一种情况, 我们需要监听一个listener加入到EventEmitter, 或者查看所有的listeners.
EventEmitter提供一个特殊的事件: newListener
var EventEmitter = require('events').EventEmitter;
class MyEmitter extends EventEmitter {}
var myEmitter = new MyEmitter();
myEmitter.on('newListener', (name, listener) => {
console.log('Event name added:', name);
});
myEmitter.on('a listener', () => {});
运行程序后输出:
Event name added: a listener
我们可以使用listeners获取所有的监听事件(针对同一个name):
var EventEmitter = require('events').EventEmitter;
class MyEmitter extends EventEmitter {}
var myEmitter = new MyEmitter();
myEmitter.on('a listener', () => {});
myEmitter.on('a listener', () => {});
// 2
console.log(myEmitter.listeners('a listener').length);
在一个大型的系统中, 例如不同的功能模块如果要进行通信, 我们也可以通过EventEmitter来实现:
var express = require('express');
var app = express();
app.on('hello-alert', function() {
console.warn('warning!');
});
app.get('/', function(req, res) {
res.app.emit('hello-alert');
res.send('hello world');
});
app.listen(3000);