在 Node.js 中,有一部分对象和函数不需要通过 require() 显式引入就可以在任何模块中直接使用,这些统称为 全局对象(Globals)。它们有些是真正的全局 global 上的属性,有些则是每个模块作用域内的“伪全局”,比如 dirname 和 filename 只在模块顶层可用。在开发日常中,熟悉这些全局对象的基本用法和边界,可以大幅减少代码中无意义的 require 调用,也让调试和程序控制更加顺手。
本节我们将逐一介绍最常用的几个全局对象:process、console、Buffer、dirname、filename,以及 global 和几个关键的全局定时器函数。
8.5.1 process:进程信息和控制中枢
process 是 Node.js 运行时提供的一个全局对象,无需引入即可使用。它代表了当前 Node.js 进程,包含了进程运行环境、命令行参数、标准 I/O 流等关键信息,也是进程生命周期控制的唯一入口。
常用属性和方法速览
| 属性/方法 | 说明 |
| ------------------------------ | ------------------------------------------------------------ |
| process.argv | 命令行参数数组,第一个元素是 Node.js 可执行文件路径,第二个是脚本路径,后续是自定义参数 |
| process.env | 当前进程的环境变量对象,常用于读取配置文件路径或敏感信息(如数据库密码) |
| process.cwd() | 返回当前工作目录,与 dirname 不同,dirname 是模块所在目录 |
| process.pid | 当前进程 ID |
| process.platform | 操作系统平台('win32'、'darwin'、'linux' 等) |
| process.exit(code) | 主动退出进程,可携带状态码(0 为正常,非 0 为异常) |
| process.nextTick(callback) | 将回调放入微任务队列,优先级高于 Promise,会在当前操作完成后立即执行 |
| process.on(event, callback) | 监听进程事件,如 'exit'(退出前)、'uncaughtException'(未捕获异常) |
| process.stdin / process.stdout / process.stderr | 标准输入、输出、错误流 |
常见场景应用
获取命令行参数
// 运行 node script.js --name=hello
console.log(process.argv);
// 输出类似: ['/usr/bin/node', '/path/to/script.js', '--name=hello']
可以借助 minimist 或 yargs 库解析参数,但简单的场景直接用 process.argv 遍历即可。
读取环境变量
大多数生产环境下,敏感配置(密钥、数据库连接)都会通过环境变量注入:
const dbPassword = process.env.DB_PASSWORD;
const nodeEnv = process.env.NODE_ENV || 'development';
if (nodeEnv === 'production') {
// 启用缓存、压缩等
}
使用 dotenv 库可以将 .env 文件中的配置加载到 process.env,实现多环境配置隔离。
退出进程
// 发生不可恢复的错误时,退出进程并返回错误码
process.exit(1);
注意,process.exit() 是直接退出,可能会导致未完成的 I/O 操作被强制中断,建议在确认所有资源已释放后使用。更好的做法是设置 process.exitCode = 1,让 Node.js 自然退出。
监听进程事件
// 捕获未处理的异常,避免进程直接崩溃(只应作为最后防线)
process.on('uncaughtException', (err) => {
console.error('未捕获异常:', err);
// 记录日志后退出
process.exit(1);
});
// 进程退出前进行清理
process.on('exit', (code) => {
console.log(`进程退出,退出码: ${code}`);
// 同步操作在这里执行,异步操作可能不会完成
});
需要警惕:uncaughtException 会捕获所有未被 try/catch 处理的异步错误,但此时的进程状态可能已不稳定,最佳实践是记录日志后重启进程。
标准 I/O 流
// 逐行读取标准输入
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('请输入你的名字: ', (answer) => {
console.log(`你好, ${answer}`);
rl.close();
});
8.5.2 console:控制台输出和调试工具
console 对象提供了向标准输出(process.stdout)和标准错误(process.stderr)打印信息的方法。它不仅仅能打印字符串,还可以输出格式化数据,甚至进行耗时统计和断言。
主要方法
| 方法 | 说明 |
| --------------------------- | -------------------------------------------------------- |
| console.log(...) | 普通日志输出,输出到标准输出 |
| console.error(...) | 错误日志,输出到标准错误,通常用于错误通知和日志收集 |
| console.warn(...) | 警告日志,输出到标准错误 |
| console.info(...) | 信息日志,输出到标准输出,行为类似于 console.log |
| console.debug(...) | 调试日志,通常只在设置了 NODE_DEBUG 环境变量时输出 |
| console.table(data) | 将数组或对象打印为表格,便于查看结构数据 |
| console.time(label) / console.timeEnd(label) | 计时开始和结束,用来测量代码块执行时间 |
| console.trace() | 打印当前调用栈,常用于调试 |
| console.assert(cond, msg) | 条件断言,不满足时输出错误信息 |
实用技巧
格式化输出
const name = 'Alice';
const age = 30;
console.log('用户 %s 的年龄是 %d', name, age); // 用户 Alice 的年龄是 30
console.log(`模板字符串更推荐: ${name}, ${age}`);
性能分析
console.time('数据库查询');
await db.query('...');
console.timeEnd('数据库查询'); // 数据库查询: 120.5ms
堆栈追踪
在深层调用中想知道调用来源:
function deepFunction() {
console.trace('调用堆栈:');
}
deepFunction();
输出将包含从入口到当前行的完整调用路径。
生产环境日志
生产环境中不应使用 console.log 打印大量日志,因为它是同步写入的,可能会阻塞事件循环。更推荐使用专业的日志库(如 pino、winston),它们支持分级、异步写盘等特性。但 console 在开发和简单脚本中仍然是最直接的调试工具。
8.5.3 Buffer:二进制数据处理的利器
在浏览器端,JavaScript 主要处理字符串,但在服务端,经常要与二进制数据打交道:文件读写、网络数据传输、图片处理、加密解密等。Buffer 类专门用于处理这些原始字节序列,它是 Node.js 中最重要的全局类型之一。
创建 Buffer
// 1. 分配指定字节长度的缓冲,内容未初始化(较快,但可能包含老数据)
const buf1 = Buffer.alloc(10); // 10 字节,全 0
// 2. 分配但不初始化(不安全,遗留数据可能暴露敏感信息)
const buf2 = Buffer.allocUnsafe(10); // 生产环境慎用,一般仅用于性能极致需求
// 3. 从数组或字符串创建
const buf3 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // Hello
const buf4 = Buffer.from('Hello', 'utf-8'); // 字符串 -> Buffer
// 4. 从 Base64 或其他编码创建
const buf5 = Buffer.from('SGVsbG8=', 'base64'); // 解码后为 'Hello'
常用操作
Buffer 与字符串互转
const str = '你好世界';
const buf = Buffer.from(str, 'utf-8'); // Buffer 占用 12 字节(每个中文字符 3 字节)
const original = buf.toString('utf-8'); // 还原为 '你好世界'
console.log(buf.toString('base64')); // 输出 Base64 编码: 5L2g5aW95LiW55WM
拼接和切片
const buf = Buffer.from('Hello World');
const slice = buf.slice(0, 5); // 获取前 5 字节,注意是共享内存!
console.log(slice.toString()); // Hello
// 修改 slice[0] = 0x48,buf 也会改变
使用 slice() 不会复制数据,而是返回指向同一块内存的视图,这类似于数组的 slice 修改会影响原 Buffer,但语义不同。若需要独立副本,可用 Buffer.from(slice)。
读写整数和浮点数
const buf = Buffer.alloc(8);
buf.writeUInt32BE(0x12345678, 0); // 大端序写入 32 位无符号整数
console.log(buf.readUInt32BE(0).toString(16)); // 12345678
buf.writeDoubleBE(3.14, 0); // 写入双精度浮点数
这类功能在解析二进制协议(如网络报文、文件头)时至关重要。
典型应用:解析 HTTP 请求体
当使用原生 http 模块处理 POST 请求时,请求体是分块传输的二进制流,需要拼接为 Buffer 后再解析字符串:
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk)); // chunk 是 Buffer
req.on('end', () => {
const body = Buffer.concat(chunks).toString();
// body 即为完整字符串
});
性能与内存
Buffer 在堆外分配内存,不受 V8 堆的垃圾回收直接管控,避免了处理大文件时因频繁 GC 导致的停顿。但也需要注意及时释放对 Buffer 的引用,避免内存泄漏。通常,离开当前作用域后引用即消失,GC 会回收堆外内存。
8.5.4 dirname 和 filename:模块路径元数据
与其他全局对象不同,dirname 和 filename 是每个模块作用域内的局部变量,并不挂在 global 上,但在任何模块直接可以用。
__filename:当前模块的完整绝对路径(含文件名)。__dirname:当前模块所在目录的完整绝对路径。
console.log(__filename);
// /home/user/project/app/index.js
console.log(__dirname);
// /home/user/project/app
典型应用
读取同目录下的文件
const path = require('path');
const fs = require('fs');
const configPath = path.join(__dirname, 'config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
切勿使用 './config.json' 这种相对路径,因为它是相对于进程的当前工作目录(process.cwd()),而不是模块所在目录。通过 __dirname 拼接可以确保路径正确,不受启动脚本位置影响。
ES 模块中的替代方案
在 ES 模块(.mjs 或 "type": "module")中,没有 dirname 和 filename,取而代之的是 import.meta.url:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
这是从 CommonJS 迁移到 ESM 时常见的一个调整。
8.5.5 global:真正的全局命名空间
global 对象类似于浏览器中的 window,是 Node.js 全局命名空间的持有者。所有真正的全局变量(如 process、console、setTimeout 等)都是 global 的属性。不过并不建议往 global 上挂载自定义属性,因为这会导致模块间的隐式依赖和命名冲突。
// 在模块 a.js
global.myVar = 'hello';
// 在模块 b.js
console.log(global.myVar); // 'hello'
除非有绝对的必要(如框架需要注入全局工具方法),否则应避免此做法。通过 exports 或依赖注入才是更清晰的模块化方式。
8.5.6 其他常用全局函数
除了上述对象,Node.js 还提供了一系列浏览器中也有或特有的全局定时器函数:
setTimeout(callback, delay)/clearTimeout(id)setInterval(callback, delay)/clearInterval(id)setImmediate(callback)/clearImmediate(id):在当前事件循环的“check”阶段执行回调,常用于将任务推迟到 I/O 回调之后。queueMicrotask(callback):将回调插入微任务队列,类似于Promise.resolve().then(cb)。
这些函数在异步控制流中扮演重要角色,它们的执行时机与事件循环的各个阶段紧密相关(详见第 3 章事件循环)。
8.5.7 小结
Node.js 提供的全局对象让开发者在不需要额外引用的情况下就能完成进程控制、日志输出、二进制数据处理、路径解析和定时调度等底层操作。它们是构建任何 Node.js 应用的基础设施,即使后来被各种高级库或框架封装,深入理解这些全局对象的用法和边界,依然是在排查诡异问题、优化性能、编写高质量系统代码时必不可少的技能。
关键要点:
process:进程生命线,环境变量、参数、退出、异常捕获都靠它。console:最直接的调试手段,但要意识到生产环境需要更专业的日志方案。Buffer:处理二进制数据的唯一标准,掌握创建、拼接、编码转换。dirname/filename:可靠地获取模块路径,避免使用相对路径。global:尽量避免污染,保持模块的封装性。
掌握这些全局对象后,你就可以在不用查找文档的情况下,自如地获取系统信息、操作二进制数据、打印调试信息,这对于快速开发和问题定位都极为实用。