Node.js 全局对象详解
1. 问题解析
在 Node.js 中,全局对象是指在任何模块中都可以直接访问的对象和变量,无需通过 require 引入。理解这些全局对象对于编写高效、规范的 Node.js 代码至关重要。
2. 核心概念
2.1 真正的全局对象
这些对象在所有模块中都可用,挂载在 global 对象上:
| 全局对象 | 说明 |
|---|---|
global |
全局命名空间对象,类似于浏览器的 window |
Buffer |
用于处理二进制数据 |
process |
当前 Node.js 进程的信息和控制 |
console |
控制台输出 |
setTimeout / clearTimeout |
定时器 |
setInterval / clearInterval |
间隔定时器 |
setImmediate / clearImmediate |
立即执行(事件循环 check 阶段) |
queueMicrotask |
微任务队列 |
2.2 模块级别的"全局"变量
这些变量虽然看起来是全局的,但实际上是每个模块私有的:
| 变量 | 说明 |
|---|---|
__dirname |
当前模块所在目录的绝对路径 |
__filename |
当前模块文件的绝对路径 |
exports |
模块导出对象的简写(指向 module.exports) |
module |
当前模块的引用 |
require |
引入其他模块的函数 |
3. 详细解答
3.1 global 对象
// global.js
// 在 global 上挂载变量,所有模块都能访问
global.myConfig = {
env: 'production',
version: '1.0.0'
};
// 其他模块中可以直接访问
console.log(myConfig); // { env: 'production', version: '1.0.0' }
注意:尽量避免污染 global,应该使用模块系统:
// 更好的做法:使用模块导出
// config.js
module.exports = {
env: 'production',
version: '1.0.0'
};
// app.js
const config = require('./config');
console.log(config.env);
3.2 Buffer 全局对象
// Buffer 用于处理二进制数据
// 创建 Buffer
const buf1 = Buffer.alloc(10); // 分配 10 字节的零填充 Buffer
const buf2 = Buffer.from('Hello'); // 从字符串创建
const buf3 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // 从数组创建
// Buffer 操作
console.log(buf2.toString()); // 'Hello'
console.log(buf2.toString('base64')); // 'SGVsbG8='
// 拼接 Buffer
const buf4 = Buffer.concat([buf2, Buffer.from(' World')]);
console.log(buf4.toString()); // 'Hello World'
3.3 process 对象
// process 提供进程信息和控制
// 环境变量
console.log(process.env.NODE_ENV);
// 命令行参数
console.log(process.argv);
// node app.js --port 3000
// ['node路径', '脚本路径', '--port', '3000']
// 进程信息
console.log(process.pid); // 进程 ID
console.log(process.platform); // 平台:darwin/linux/win32
console.log(process.version); // Node.js 版本
// 进程事件
process.on('exit', (code) => {
console.log(`进程退出,代码: ${code}`);
});
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
process.exit(1);
});
// 当前工作目录
console.log(process.cwd());
// 内存使用
console.log(process.memoryUsage());
// {
// rss: 23456789,
// heapTotal: 12345678,
// heapUsed: 9876543,
// external: 1234567,
// arrayBuffers: 12345
// }
3.4 __dirname 和 __filename
// 假设文件路径: /Users/project/src/app.js
console.log(__dirname);
// /Users/project/src
console.log(__filename);
// /Users/project/src/app.js
// 常用:构建绝对路径
const path = require('path');
const configPath = path.join(__dirname, '../config.json');
3.5 exports 和 module.exports
// 导出方式 1:exports(exports 是 module.exports 的引用)
// math.js
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
// 导出方式 2:module.exports
// calculator.js
module.exports = {
multiply: (a, b) => a * b,
divide: (a, b) => a / b
};
// 导出方式 3:导出类
// User.js
class User {
constructor(name) {
this.name = name;
}
}
module.exports = User;
// 使用
const math = require('./math');
const Calculator = require('./calculator');
const User = require('./User');
console.log(math.add(1, 2)); // 3
console.log(Calculator.multiply(2, 3)); // 6
const user = new User('Alice');
重要区别:
// 这样写会切断 exports 和 module.exports 的关联
exports = { foo: 'bar' }; // 错误!
// 正确写法
module.exports = { foo: 'bar' };
// 或
exports.foo = 'bar'; // 修改的是引用的对象
3.6 require 函数
// require 加载模块
const fs = require('fs'); // 内置模块
const express = require('express'); // npm 包
const utils = require('./utils'); // 相对路径文件
const config = require('/absolute/path/config'); // 绝对路径
// require 特性
console.log(require.main); // 入口模块
console.log(require.resolve('fs')); // 解析模块路径
console.log(require.cache); // 模块缓存
// 清除缓存(热更新场景)
delete require.cache[require.resolve('./utils')];
4. 深入理解
4.1 模块包装器
Node.js 在执行模块代码前,会用函数包装器包裹:
(function(exports, require, module, __filename, __dirname) {
// 模块代码实际在这里执行
// 这就是为什么 __dirname 等看起来是全局的,
// 但实际上是函数参数
});
这就是为什么每个模块的 exports、require 等是独立的。
4.2 global 与模块作用域
// 模块 A
const localVar = 'I am local';
global.globalVar = 'I am global';
// 模块 B
console.log(globalVar); // 'I am global' - 可以访问
console.log(localVar); // ReferenceError - 无法访问
4.3 严格模式下的差异
'use strict';
// 非严格模式下,this 指向 global
// 严格模式下,this 是 undefined
console.log(this); // undefined
// 但 global 对象仍然可用
console.log(global); // 全局对象
5. 最佳实践
5.1 避免污染 global
// 不推荐
global.db = require('./db');
global.config = require('./config');
// 推荐:使用依赖注入或模块系统
// db.js
module.exports = createDBConnection();
// app.js
const db = require('./db');
const config = require('./config');
function initializeApp({ db, config }) {
// 通过参数传递依赖
}
5.2 合理使用 __dirname
const path = require('path');
// 构建跨平台路径
const dataPath = path.join(__dirname, 'data', 'users.json');
// 解析相对路径为绝对路径
const absolutePath = path.resolve(__dirname, '../config');
// ES Module 中 __dirname 不可用,需要这样获取
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
5.3 模块导出规范
// 统一导出风格
// 工具函数库 - 使用命名导出
exports.formatDate = (date) => { /* ... */ };
exports.parseJSON = (str) => { /* ... */ };
// 单一功能类 - 使用默认导出
class Logger {
// ...
}
module.exports = Logger;
// 混合导出
class Service {
// ...
}
Service.VERSION = '1.0.0';
module.exports = Service;
6. 面试要点
-
区分真正的全局对象和模块变量
Buffer、process、global是真正的全局对象__dirname、__filename、require、exports是模块级别的函数参数
-
exports 与 module.exports 的区别
exports是module.exports的引用- 直接给
exports赋值会切断关联 - 最终导出的是
module.exports指向的对象
-
ES Module 与 CommonJS 的差异
- ES Module 中没有
__dirname、__filename、require - 需要使用
import.meta.url和fileURLToPath模拟 - ES Module 的
this是undefined
- ES Module 中没有
-
global 对象的使用场景
- 单元测试中的全局配置
- 极特殊场景下的跨模块共享(不推荐)
- 框架级别的全局注入(如某些测试框架)
-
Buffer 的注意事项
Buffer.alloc()vsBuffer.allocUnsafe()的安全性差异Buffer.from()的多种构造方式- Node.js 6.0 后
new Buffer()被废弃的原因(安全性)