本文深入解析Node.js的核心原理,重点阐述阻塞与非阻塞代码的执行差异,以及基于回调函数的异步编程模式。通过实战代码演示EventEmitter类的创建、事件绑定与触发机制,并探讨如何在自定义对象中继承Event以构建支持事件响应的核心模块。
基本概念
简单来说,Node.js 是一种在服务器端运行的 JavaScript 运行环境。
$ node
> console.log('Hello 黎燃!');
Hello 黎燃!然而,Node.js 的概念与传统的 JavaScript 有着本质的不同。
在使用 Node.js 时,我们不仅是在开发应用程序,实际上也是在构建整个 HTTP 服务器。
事实上,我们的 Web 应用程序与对应的 Web 服务器在结构上基本一致。让我们了解一下 Node.js 应用程序的组成部分: 1. 加载所需模块:我们可以使用 require 命令来加载 Node.js 模块。 2. 创建服务器:服务器可以监听客户端的请求,其功能类似于 Apache 和 Nginx 等 HTTP 服务器。 3. 接收并响应请求:服务器很容易创建。客户端可以通过浏览器或终端发送 HTTP 请求,服务器接收到请求后会返回相应的数据。
回调函数
Node.js 中异步编程的直接体现就是回调机制。
阻塞代码
异步编程依赖于回调,但这并不意味着使用了回调函数程序就一定是异步的。任务完成后会调用回调函数,Node.js 中大量使用了回调函数,且其所有 API 都支持回调机制。
在读取文件后,我们将文件内容作为回调函数的参数返回。这样,在执行后续代码时,就不会阻塞或等待文件 I/O 操作的完成。
回调函数通常作为函数的最后一个参数出现:
function foo1(name, age, callback) { }
function foo2(value, callback1, callback2) { }创建主 JS 文件,代码如下:
var fs = require("fs");
var data = fs.readFileSync('input.txt');
console.log(data.toString());
console.log("程序执行结束!");非阻塞代码
创建主 JS 文件,代码如下:
var fs = require("fs");
fs.readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
console.log("程序执行结束!");通过上述两个示例,我们可以理解阻塞调用与非阻塞调用的区别。第一个实例在读取文件后才会执行后续程序;而在第二个示例中,我们无需等待文件读取完成,可以在读取文件的同时执行后续代码,这大大提高了程序的性能。
因此,阻塞代码需要按顺序执行,而非阻塞代码则不需要严格遵循顺序。
Node.js 事件循环

Node.js 采用事件驱动模型。当 Web 服务器接收到一个请求时,它会处理该请求,然后为下一个 Web 请求提供服务。当请求处理完成时,它会被放回处理队列。当它到达队列头部时,结果将返回给用户。接下来我们引入 events 模块。
var events = require('events');创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();绑定事件及事件的处理程序
eventEmitter.on('eventName', eventHandler);触发事件
eventEmitter.emit('eventName');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("程序执行完毕");在上述程序中,fs.readFile() 是一个用于读取文件的异步函数。如果在读取文件期间发生错误,error err 对象将输出错误消息。
程序执行完毕
如果没有发生错误,readfile 将跳过 err 对象的输出,并通过回调函数输出文件内容。
接下来,我们删除输入 txt 文件。
执行结果如下:
程序执行完毕 Error: ENOENT, open 'input.txt'
EventEmitter 类
每次有新连接时,服务器对象的 net 将触发事件,而 readstream 对象的 fs 将在文件打开时触发事件。所有生成事件的对象都是 EventEmitter 实例中的事件。
引入 events 模块
var events = require('events');创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();如果在实例化 EventEmitter 对象期间发生错误,将触发 error 事件。添加新监听器时,将触发 newListener 事件。删除监听器时,将触发 removeListener 事件。
var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
event.on('some_event', function() {
console.log('some_event 事件触发');
});
setTimeout(function() {
event.emit('some_event');
}, 1000); 运行此代码,控制台在 1 秒后输出 'some'。触发事件时,将依次调用注册到此事件的事件监听器,并将事件参数作为回调函数参数传递。
var events = require('events');
var emitter = new events.EventEmitter();
emitter.on('someEvent', function(arg1, arg2) {
console.log('listener1', arg1, arg2);
}); on(event, listener)
注册指定事件的监听器,接受字符串事件名和回调函数。
server.on('connection', function (stream) {
console.log('someone connected!');
});继承 EventEmitter
大多数时候,我们不直接使用 EventEmitter,而是在对象中继承它。所有支持事件响应的核心模块,包括 fs、net 和 http,都是 EventEmitter 的子类。
- 1. 首先,具有实体函数的对象实现了符合语义的事件。事件的监听和发生应该是对象的一种方法。
- 2. 其次,JavaScript 的对象机制是基于原型的,支持部分多重继承。继承 EventEmitter 不会干扰对象的原始继承关系。
到此这篇关于 Node.js 原理对于阻塞和 EventEmitter 及其继承的运用实战的文章就介绍到这了,更多相关 Node.js EventEmitter 内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!