FAQ?
简单实现Node的Events模块
Node.js 的 events 模块是发布订阅模式的实现,几乎所有 Node 内置模块(如 http、stream、fs)都基于它。下面是一个简化版的实现:
class EventEmitter {
constructor() {
this._events = {}; // 存储事件名 -> 监听器数组
}
// 注册监 听器,追加到队列末尾
on(eventName, listener) {
if (!this._events[eventName]) {
this._events[eventName] = [];
}
this._events[eventName].push(listener);
return this;
}
// 注册一次性监听器,触发后自动移除
once(eventName, listener) {
const wrapper = (...args) => {
listener(...args);
this.off(eventName, wrapper);
};
// 标记原始监听器,方便 off 时查找
wrapper._raw = listener;
this.on(eventName, wrapper);
return this;
}
// 触发事件,依次执行对应监听器
emit(eventName, ...args) {
const listeners = this._events[eventName];
if (!listeners) return false;
// 拷贝一份,防止监听器内部 off 导致遍历错乱
[...listeners].forEach((fn) => fn(...args));
return true;
}
// 移除监听器
off(eventName, listener) {
if (!this._events[eventName]) return this;
this._events[eventName] = this._events[eventName].filter(
(fn) => fn !== listener && fn._raw !== listener
);
if (this._events[eventName].length === 0) {
delete this._events[eventName];
}
return this;
}
// 移除某事件的所有监听器,或全部事件
removeAllListeners(eventName) {
if (eventName) {
delete this._events[eventName];
} else {
this._events = {};
}
return this;
}
}
使用示例:
const emitter = new EventEmitter();
emitter.on('login', (user) => console.log(`${user} 登录了`));
emitter.once('boot', () => console.log('系统启动'));
emitter.emit('boot'); // 系统启动
emitter.emit('boot'); // 不再触发
emitter.emit('login', 'Tom'); // Tom 登录了
实现要点:
- 使用对象
_events以事件名为 key 存储监听器数组。 emit时先拷贝监听器列表,避免监听器内部移除自身导致遍历异常。once通过包装函数 +off实现,同时保留_raw引用,便于off匹配原始函数。- Node 原生
EventEmitter还支持prependListener、setMaxListeners、error事件特殊处理等,可按需扩展。
在nodejs中使用import语句
在 Node.js 中,使用 import 语句可以导入模块。但是需要注意的是,Node.js 默认是不支持 ES6 的 import 语法的,而是使用 CommonJS 的 require() 语法。
如果要在 Node.js 中使用 import 语句,需要在代码中使用 ES6 模块的语法,并且需要通过一些方式来将其转换为 CommonJS 模块的语法。
有几种方式可以实现这一点:
- 使用 Babel 转换:使用 Babel 可以将 ES6 的
import语句转换为 CommonJS 的require()语法。你需要安装@babel/core和@babel/node,并创建一个 Babel 配置文件来指定需要转换的代码。 - 使用 TypeScript:TypeScript 本身就支持 ES6 模块的语法,并且可以编译为 CommonJS 的模块语法。你需要安装
typescript,并创建一个 TypeScript 配置文件来指定需要编译的代码。 - 使用
esm模块:esm是一个第三方的模块,可以在 Node.js 中使用 ES6 模块的语法。你需要安装esm模块,并在代码中使用import语法。使用esm的方式比较简单,但是需要注意的是它并不是 Node.js 的标准模块,可能存在一些不兼容的问题。
下面是使用 esm 模块的示例代码:
javascriptCopy code
// 安装 esm 模块
npm install --save esm
// index.js
require = require("esm")(module/*, options*/);
module.exports = require("./main.js");
// main.js
import { foo } from "./foo.js";
console.log(foo);
在上面的示例代码中,我们使用 esm 模块将 ES6 模块语法转换为 CommonJS 的模块语法。在 index.js 文件中,我们将默认的 require() 函数替换为 esm 的 require() 函数,并将其导出为 Node.js 模块的语法。在 main.js 文件中,我们可以直接使用 import 语法来导入其他模块。
如果您使用的是高版本的 Node.js(v13.2.0 或更高版本),则可以直接使用 ES6 的 import 语法,无需进行转换。
以下是一个简单的示例代码:
javascriptCopy code
// 导入模块
import { createServer } from 'http';
// 创建 HTTP 服务器
const server = createServer((req, res) => {
res.end('Hello, World!');
});
// 启动服务器
server.listen(3000, () => {
console.log('Server is running on port 3000');
});