关于 Node.js
Node.js 就是运行在服务端的 JavaScript。Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。
- 安装后,
node -v
进行版本检测。 - 执行
node xxx.js
,运行xxx.js脚本。 - Node.js支持
REPL
交互式模式,执行node
命令后进入交互模式。 - Node.js使用
require
进行模块的引入。 - Node.js可以创建类似
Apache``Nginx
一样的HTTP服务器,用于处理客户端的请求和响应。
搭建第一个Node.js HTTP服务器
#server.js
var http = require('http');
http.createServer(function(req, res) {
// 设置HTTP状态值为200,内容类型为text/plain
res.writeHead(200, {'Content-Type': 'text/plain'});
// 发送响应数据
res.end('hello world');
}).listen(8888);
console.log('Server running at http://172.0.0.1:8888/');
使用require
加载引入http
模块,使用该模块的createServer
函数创建HTTP服务器,listen
方法设置监听端口。在命令中输入node server.js
启动这个node.js HTTP服务器。并在浏览器和命令行中进行测试访问,如下图示:
Node.js REPL交互模式
在REPL
交互模式下,可以进行文件的读取、语句的执行、console打印等。演示如下:
Node.js 异步编程之回调函数
Node.js 异步编程的直接体现就是回调函数。异步编程依托于回调来实现,但不能说使用了回调函数后程序就异步化了。回调函数是在完成任务后被调用的,Node 使用了大量的回调函数,Node.js中所有的 API 都支持回调函数。回调函数一般作为函数的最后一个参数出现。
function foo1 (name, age, callback) { }
function foo2 (value, callback1, callback2) { }
例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数进行返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。
阻塞(同步编程):阻塞是按顺序执行的。
// sync.js 测试 阻塞
var fs = require('fs');
var data = fs.readFileSync('input.txt');
console.log(data.toString());
console.log('程序执行结束!');
非阻塞(异步编程):非阻塞不需要按顺序执行,如果要处理异步执行的结果,在回调函数里去实现。
// async.js 测试 非阻塞
var fs = require('fs');
fs.readFile('input.txt', function(err, data) {
if (err) return console.error(err);
if (data) {
console.log(data.toString());
}
});
console.log('程序执行结束!');
Node.js 应用程序是如何工作的?
var fs = require('fs');
fs.readFile('input.txt', function(err, data) {
if (err) {
console.log(err.stack);
return;
}
console.log(data.toString());
});
console.log('程序执行完毕');
在Node.js 程序中,异步函数执行时,接收一个回调函数作为最后一个参数,该回调参数的第一个参数是err错误对象。当这个异步操作执行错误时,就会生成err错误对象;当没有错误发生时,就会跳过err对象的输出。
Node.js 事件循环模型
Node.js 是单进程单线程的应用程序,但基于 V8 引擎提供的异步执行回调接口,可以处理大量的并发操作,所以其性能非常高。每个异步事件都会生成一个事件观察者,如果有事件发生就会调用该回调函数。Node.js 单线程类似进入一个while(true)
的事件循环,直到没有事件观察者退出。Node.js 所有的事件机制,基本上都采用了设计模式中观察者模式。
事件驱动模型
Node.js 使用事件驱动模型,当web server接收到请求后,立即关闭它然后进行处理,接着去服务下一个web请求。当这个请求完成后,它会被放回处理队列,当到达队列的开头时,这个处理结果将被返回给用户。
这个模型非常高效、可扩展性非常强,因为web server只是接受请求,而不等待任何的读写操作,这被称之为非阻塞式IO
或者事件驱动IO
。
在事件驱动模型中,会生成一个主循环来监听所有的事件,当检测到事件时将触发回调函数。
事件驱动模型整个事件驱动的流程就是这么实现的,非常简洁。这类似于观察者模式,事件相当于一个主题(Subject
),而所有注册到这个事件上的处理函数都相当于是观察者(Observer
)。
Node.js 有多个内置的事件,我们可以通过引入 events
模块,并通过实例化 EventEmitter
类来绑定并监听事件,如下实例:
var events = require('events');
// 创建EventEmitter事件模型对象
var ee = new events.EventEmitter();
// 监听connection事件
ee.on('connection', function() {
console.log('1-连接成功');
ee.emit('data_received');
});
// 监听data_received事件
ee.on('data_received', function() {
console.log('2-数据接收成功');
});
// 触发connection事件
ee.emit('connection');
console.log('3-程序执行完毕');
events.EventEmitter类
在Node.js中,所有的异步I/O操作在完成时都会发送一个事件到事件队列。events模块只提供了一个对象,即events.EventEmitter
类,它的核心就是事件监听功能与事件触发功能的封装。同一个EventEmitter实例,可以同时监听多个同名事件:
var events = require('events');
var ee = new events.EventEmitter();
ee.on('some', function(arg1, arg2) {
console.log(arg1, arg2);
});
ee.on('some', function(arg1, arg2) {
console.log(arg1, arg2);
});
ee.on('some', function(arg1, arg2) {
console.log(arg1, arg2);
});
ee.emit('some', '参数1', '参数2');
大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter
的子类。
Buffer 缓冲区
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。Buffer 库为 Node.js 带来了一种存储原始数据的方法。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。
// 创建Buffer实例
const buf = Buffer.from('geekxia', 'ascii');
console.log(buf);
console.log(buf.toString('hex'));
console.log(buf.toString('base64'));
// 写入缓冲区
buf.write()
// 从缓冲区中读取数据
buf.toString()
// 把Buffer转换为JSON对象
buf.toJSON()
// 合并缓冲区
Buffer.concat([buf1, buf2]);
// 缓冲区比较
buf.compare(buf1);
// 缓冲区拷贝
buf.copy()
// 缓冲区裁剪
buf.slice()
// 读取缓冲区的长度
buf.length;
Stream 流
Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http 服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。所有的 Stream 对象都是 EventEmitter 的实例,常用的事件有:
-
data
- 当有数据可读时触发。 -
end
- 没有更多的数据可读时触发。 -
error
- 在接收和写入过程中发生错误时触发。 -
finish
- 所有数据已被写入到底层系统时触发。
可读流、可写流
使用fs.createReadStream()
创建可读流
,使用fs.createWriteStream()
还可以创建可写流
。
var fs = require('fs');
var data = '';
// 创建可读流
var rs = fs.createReadStream('input.txt');
rs.setEncoding('UTF8');
// 处理流事件 - data, end, error
rs.on('data', function(chunk) {
data += chunk;
});
rs.on('end', function() {
console.log(data);
});
rs.on('error', function(err) {
console.log(err.stack);
});
console.log('程序执行完毕');
管道流
管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。
管道流var fs = require('fs');
// 创建一个可读流
var rs = fs.createReadStream('input.txt');
// 创建一个可写流
var ws = fs.createWriteStream('output.txt');
// 管道读写操作:读取input.txt中的内容,并将其写入到output.txt中去
rs.pipe(ws);
console.log('程序执行完毕');
链式流
链式流是通过连接输出流
到另外一个流并创建多个流操作链的机制。链式流一般用于管道操作。接下来我们就是用管道和链式来实现文件的压缩和解压
。
实现压缩:compress.js
var fs = require('fs');
var zlib = require('zlib');
// 压缩input.txt 为 input.txt.gz
fs.createReadStream('input.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('input.txt.gz'));
console.log('文件压缩完成');
实现解压:decompress.js
var fs = require('fs');
var zlib = require('zlib');
// 把 input.txt.gz 解压为 input2.txt
fs.createReadStream('input.txt.gz')
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream('input2.txt'));
console.log('文件解压完成');
Node.js 完整教程 第01篇,结束!
网友评论