返回首页

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 等看起来是全局的,
  // 但实际上是函数参数
});

这就是为什么每个模块的 exportsrequire 等是独立的。

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. 面试要点

  1. 区分真正的全局对象和模块变量

    • Bufferprocessglobal 是真正的全局对象
    • __dirname__filenamerequireexports 是模块级别的函数参数
  2. exports 与 module.exports 的区别

    • exportsmodule.exports 的引用
    • 直接给 exports 赋值会切断关联
    • 最终导出的是 module.exports 指向的对象
  3. ES Module 与 CommonJS 的差异

    • ES Module 中没有 __dirname__filenamerequire
    • 需要使用 import.meta.urlfileURLToPath 模拟
    • ES Module 的 thisundefined
  4. global 对象的使用场景

    • 单元测试中的全局配置
    • 极特殊场景下的跨模块共享(不推荐)
    • 框架级别的全局注入(如某些测试框架)
  5. Buffer 的注意事项

    • Buffer.alloc() vs Buffer.allocUnsafe() 的安全性差异
    • Buffer.from() 的多种构造方式
    • Node.js 6.0 后 new Buffer() 被废弃的原因(安全性)