Node.js 入门
常用库
接口信息来源于 nodejs.cn
module
在 Node.js 模块系统中,每个文件都被视为一个独立的模块。
Export Object
//test.js
exports.msg = 'Hello world';
//main.js
var test = require('./test.js');
console.log(test.msg);
Export Function
//test.js
module.exports = function (msg) {
console.log(msg);
};
//main.js
var msg = require('./test.js');
msg('Hello World');
Export function as a class
//test.js
module.exports = function (firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.fullName = function () {
return this.firstName + ' ' + this.lastName;
}
}
//main.js
var person = require('./Person.js');
var person1 = new person('James', 'Bond');
console.log(person1.fullName());
Buffer
Buffer为Node.js操作二进制字节流的库。每单位为八位字节流
const buf1=Buffer.alloc(10) //创建一个长度为10,零填充的Buffer
const buf2=Buffer.alloc(10,1) //创建一个长度为10,0x01 填充的Buffer
const buf3=Buffer.from([1,2,3]) //创建一个[0x01,0x02,0x03]的Buffer
const buf4=Buffer.from('teststring')
const buf5=Buffer.from(buf4) //创建一个buf4的拷贝
const buf6=Buffer.concat([buf1,buf2,buf3],totalLength) //拼接Buffer,totalLength可不指定
const str1=buf4.toString('utf8', 0, 4) //test
events
所有能触发事件的对象都是 EventEmitter
类的实例。 这些对象有一个 eventEmitter.on()
函数,用于将一个或多个函数绑定到命名事件上。 事件的命名通常是驼峰式的字符串。
eventEmitter.emit()
方法可以传任意数量的参数到监听器函数。
当 EventEmitter
对象触发一个事件时,所有绑定在该事件上的函数都会被同步地调用。
const EventEmitter = require('events');
const myEmitter=new EventEmitter();
myEmitter.on('event',(a,b)=>{
console.log(`${a}+${b}=${a+b}`);
});
myEmitter.on('event',(a,b)=>{
console.log(`${a}-${b}=${a-b}`);
});
myEmitter.emit('event',3,5);
// 3+5=8
// 3-5=-2
使用 eventEmitter.once()
可以注册最多可调用一次的监听器。 当事件被触发时,监听器会被注销,然后再调用。
const EventEmitter = require("events");
const myEmitter = new EventEmitter();
let m = 0;
myEmitter.once('event', () => {
console.log(++m);
});
myEmitter.emit('event');
// 1
myEmitter.emit('event');
// 不触发
fs
fs
模块提供了一个 API,用于以模仿标准 POSIX 函数的方式与文件系统进行交互。
所有文件系统操作都具有同步和异步的形式。
异步的形式总是将完成回调作为其最后一个参数。 传给完成回调的参数取决于具体方法,但第一个参数始终预留用于异常。 如果操作成功完成,则第一个参数将为 null
或 undefined
。
使用异步的方法时无法保证顺序。 因此,以下的操作容易出错,因为 fs.stat()
操作可能在 fs.rename()
操作之前完成:
fs.rename('/tmp/hello', '/tmp/world', (err) => {
if (err) throw err;
console.log('重命名完成');
});
fs.stat('/tmp/world', (err, stats) => {
if (err) throw err;
console.log(`文件属性: ${JSON.stringify(stats)}`);
});
要正确地排序这些操作,则将 fs.stat()
调用移动到 fs.rename()
操作的回调中:
fs.rename('/tmp/hello', '/tmp/world', (err) => {
if (err) throw err;
fs.stat('/tmp/world', (err, stats) => {
if (err) throw err;
console.log(`文件属性: ${JSON.stringify(stats)}`);
});
});
stream
流(stream)是 Node.js 中处理流式数据的抽象接口。 stream
模块用于构建实现了流接口的对象。HTTP请求process.stdout
都是流的实例。可以用fs.createWtireStream()等来创建文件流。
流有4种类型:可读流,可写流,可读可写流以及Transform流。
所有的流都是EventEmitter
的实例。
const fs = require("fs");
const w = fs.createWriteStream("file.txt");
w.write("hello");
w.end(",world!");
//cat file.txt
//hello,world!
流与流之间可以用管道来推送数据。可以给可读流绑定多个可写流。
const fs = require('fs');
const r = fs.createReadStream('file.txt');
const z = zlib.createGzip();
const w = fs.createWriteStream('file.txt.gz');
r.pipe(z).pipe(w);
http
一个基于流实现的http server
const http = require('http');
const server = http.createServer((req, res) => {
// req 是一个 http.IncomingMessage 实例,它是可读流。
// res 是一个 http.ServerResponse 实例,它是可写流。
let body = '';
// 接收数据为 utf8 字符串,
// 如果没有设置字符编码,则会接收到 Buffer 对象。
req.setEncoding('utf8');
// 如果添加了监听器,则可读流会触发 'data' 事件。
req.on('data', (chunk) => {
body += chunk;
});
// 'end' 事件表明整个请求体已被接收。
req.on('end', () => {
try {
const data = JSON.parse(body);
// 响应信息给用户。
res.write(typeof data);
res.end();
} catch (er) {
// json 解析失败。
res.statusCode = 400;
return res.end(`错误: ${er.message}`);
}
});
});
server.listen(1337);
// $ curl localhost:1337 -d "{}"
// object
// $ curl localhost:1337 -d "\"foo\""
// string
// $ curl localhost:1337 -d "not json"
// 错误: Unexpected token o in JSON at position 1
net
net
模块用于创建基于流的 TCP 或 IPC 的服务器与客户端。
一个监听8124端口并将输入发回给客户端的例子如下
const net = require("net");
const server = net.createServer(c => {
console.log("客户端已连接");
c.on("end", () => {
console.log("客户端已断开连接");
});
c.write("你好\r\n");
c.pipe(c);
});
server.on("error", err => {
throw err;
});
server.listen(8124, () => {
console.log("服务器已启动");
});
url
url
模块用于处理与解析 URL。
WHATWG标准的新API可直接按如下方式使用
const url=new URL('https://www.google.com/search?q=test&oq=test&sourceid=chrome')
console.log(url)
/* URL {
* href:
* 'https://www.google.com/search?q=test&oq=test&sourceid=chrome',
* origin: 'https://www.google.com',
* protocol: 'https:',
* username: '',
* password: '',
* host: 'www.google.com',
* hostname: 'www.google.com',
* port: '',
* pathname: '/search',
* search: '?q=test&oq=test&sourceid=chrome',
* searchParams:
* URLSearchParams { 'q' => 'test', 'oq' => 'test', 'sourceid' => 'chrome' },
* hash: '' }
*/
异步与事件循环
Node.js中大部分I/O操作为异步操作,处理I/O的操作结果等可通过回调函数来实现,但这样会导致代码可读性变差,异常难以排查等后果。因此可以考虑Promise,async等库来进行异步处理。
下面主要介绍异步的执行顺序。
const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => {
console.log('foo')
setTimeout(bar, 0)
baz()
}
foo()
// output:
// foo
// baz
// bar
按照直观上的执行顺序来说,bar应该是立即执行的,在这种理解下输出顺序应该为foo,bar,baz。但结果并不是这样。
造成这样结果的原因是由于Node.js的异步是基于消息队列实现的。
当程序执行至setTimeout时,会将其交给定时器触发,由于延时为0,因此立即触发,并将其置入消息队列中,然后继续向下执行。遇到baz。这时还没有输出bar。
当执行栈为空时,这是从消息队列中取出一条任务加入执行栈并执行,这时才会输出bar。
也就是说,回调的执行需要等执行栈为空时,才会从消息队列中将其取出并执行。
但是当setTimeout与Promise一起使用时,又会出现新的问题。
console.log("A");
setTimeout(() => {
console.log("A - setTimeout");
}, 0);
new Promise((resolve) => {
resolve();
})
.then(() => {
return console.log("A - Promise 1");
})
.then(() => {
return console.log("B - Promise 1");
});
new Promise((resolve) => {
resolve();
})
.then(() => {
return console.log("A - Promise 2");
})
.then(() => {
return console.log("B - Promise 2");
})
.then(() => {
return console.log("C - Promise 2");
});
console.log("AA");
// A
// AA
// A - Promise 1
// A - Promise 2
// B - Promise 1
// B - Promise 2
// C - Promise 2
// A - setTimeout
setTimeoue先入队,却后输出。
造成这样的原因是由于他们入的是不同的队列,他们的优先级不同。(实际的情况可以参考阮一峰的文章 )
具体的优先级对比可参考下面的代码。
setImmediate(function() {
console.log(1);
}, 0);
setTimeout(function() {
console.log(2);
}, 0);
new Promise(function(resolve) {
console.log(3);
resolve();
console.log(4);
}).then(function() {
console.log(5);
});
console.log(6);
process.nextTick(function() {
console.log(7);
});
console.log(8);
// 3
// 4
// 6
// 8
// 7
// 5
// 2
// 1
//process.nextTick > promise.then > setTimeout > setImmediate;