一、Node.js全局对象是什么
浏览器里有个window对象,挂在它上面的属性和方法可以在页面任何地方访问。Node.js对应的根级对象叫global。挂在global上的变量和函数,在整个应用范围内都能访问,不需要require导入。
但这里有个容易混淆的点:Node.js的“全局”和浏览器的“全局”并不一样。Node.js每个文件是一个模块,模块内声明的变量默认只在该模块作用域内有效。只有显式挂到global上的东西,才真正跨模块共享。
为什么需要全局对象?几个典型场景:
-
进程管理:
process提供进程信息、环境变量、命令行参数; -
路径处理:
__filename、__dirname定位当前文件和目录; -
定时调度:
setTimeout、setInterval、setImmediate、queueMicrotask; -
二进制数据:
Buffer处理文件、网络流等原始字节; -
模块系统:
exports、require管理模块导入导出。
本节课程知识要点:
global是Node.js的根级对象,但模块内变量默认不共享;全局对象的价值在于提供跨模块可访问的基础能力。
二、global与globalThis
2.1global对象
global.message = "这是全局变量";
console.log(message);
输出:
这是全局变量
把message挂到global上之后,当前应用的其他模块也能通过message直接访问。
2.2globalThis对象
globalThis是ECMAScript标准引入的全局对象引用,目的是让同一套代码在浏览器和Node.js里都能拿到全局作用域。
console.log(globalThis === global);
globalThis.message = "欢迎学习代码号编程";
console.log(globalThis.message);
输出:
true
欢迎学习代码号编程
在Node.js中globalThis===global返回true,两者指向同一个对象。
为什么用globalThis而不是global:如果一段代码需要同时跑在浏览器和Node.js环境(比如同构工具库),写globalThis比写global更通用。写global在浏览器里会直接报ReferenceError。
踩坑经历:早期做跨环境工具函数时,我用typeofwindow!=='undefined'?window:global来判断环境,代码又长又容易出错。换成globalThis后一行搞定。但要注意,globalThis在较老的Node.js版本里不存在,如果项目需要兼容旧版本,得先确认运行时版本。
三、console对象
console是全局对象,负责向终端输出日志和调试信息,写入stdout和stderr。
console.log("普通日志");
console.error("错误日志");
输出:
普通日志
错误日志
console.log()写stdout,console.error()和console.warn()写stderr。这个流向差异在重定向日志时很关键。
四、process对象
process代表当前运行的Node.js进程,提供运行时环境信息和控制能力。
console.log("进程 ID:", process.pid);
console.log("Node.js 版本:", process.version);
console.log("当前工作目录:", process.cwd());
console.log("环境变量 NODE_ENV:", process.env.NODE_ENV);
输出类似:
进程 ID: 6934
Node.js 版本: v22.21.1
当前工作目录: /Users/alan/projects/demo
环境变量 NODE_ENV: development
process.pid是进程号,process.version是Node.js版本,process.env读取环境变量,process.cwd()返回当前工作目录。
项目反思:以前我在代码里硬编码配置路径,换环境就出错。后来统一用process.env读取配置,配合.env文件管理,部署时只改环境变量,代码不动。process.env.NODE_ENV也是区分开发和生产行为的常用手段,但注意它只是个字符串,别指望框架自动帮你做所有判断。
五、Buffer对象
Buffer用于在内存中直接处理原始二进制数据,比如文件读写、网络流。
const buffer = Buffer.from("Hello Node.js");
console.log(buffer);
输出是字符串的UTF-8字节的十六进制表示:
<Buffer 48 65 6c 6c 6f 20 4e 6f 64 65 2e 6a 73>
Buffer.from()把字符串转成字节序列。Buffer工作在JavaScript字符串编码体系之外,适合处理非文本数据。
个人建议:处理文本优先用字符串,只有涉及二进制(图片、加密、网络协议)时才用Buffer。混用容易在编码上踩坑,比如把Buffer直接当字符串拼接,结果会变成十六进制文本。
六、内置全局定时器函数
| 函数 | 作用 |
|---|---|
setTimeout() |
延迟指定时间后执行一次 |
setInterval() |
每隔指定时间重复执行,直到被清除 |
setImmediate() |
在当前事件循环迭代结束后立即执行 |
queueMicrotask() |
把函数排入微任务队列,在当前操作完成后、事件循环继续前执行 |
看一个综合示例:
console.log("代码号编程定时器示例");
setTimeout(() => {
console.log("setTimeout()");
}, 0);
const interval = setInterval(() => {
console.log("setInterval()");
clearInterval(interval);
}, 1000);
setImmediate(() => {
console.log("setImmediate()");
});
queueMicrotask(() => {
console.log("queueMicrotask()");
});
console.log("End");
输出顺序大致为:
代码号编程定时器示例
End
queueMicrotask()
setImmediate()
setTimeout()
setInterval()
执行顺序拆解:
-
同步代码先跑:打印标题和
End; -
queueMicrotask()排在微任务队列,当前同步代码结束后立即执行; -
setImmediate()在当前事件循环迭代的check阶段执行; -
setTimeout(...,0)进入timers阶段,实际会有最小延迟; -
setInterval()在1秒后触发,执行一次后立即清除。
注意:setImmediate()和setTimeout()的先后顺序在不同执行上下文和事件循环状态下可能变化,不要依赖固定顺序做业务逻辑。
七、模块相关全局变量
这些变量在每个模块里都能用,管理文件路径和模块导入导出。
| 变量/函数 | 说明 |
|---|---|
__dirname |
当前模块所在目录的绝对路径 |
__filename |
当前模块文件的绝对路径 |
exports |
导出函数、对象或值的快捷方式 |
require() |
加载模块,使用其导出的功能 |
示例:
maths.js
console.log("__dirname:", __dirname);
console.log("__filename:", __filename);
exports.add = (a, b) => a + b;
app.js
const maths = require("./maths");
console.log("sum:", maths.add(10, 20));
输出类似:
__dirname: C:\Projects\NodeApp
__filename: C:\Projects\NodeApp\maths.js
sum: 30
__dirname和__filename显示maths.js的目录和文件路径;exports导出add()函数;require()在app.js里加载该模块并调用。
踩坑经历:__dirname在CommonJS模块里可用,但在ESModule(.mjs或"type":"module")里不存在。迁移项目时我遇到一批文件报__dirnameisnotdefined,后来改用import.meta.url配合fileURLToPath转换。如果项目混用两种模块规范,这个差异要提前注意。
八、现在Node.js的Web平台全局API
较新的Node.js版本引入了浏览器风格的全局API,服务端也能直接用。
| 全局API | 说明 |
|---|---|
fetch() |
发送HTTP请求并接收响应 |
URL |
创建和操作URL |
URLSearchParams |
解析和管理URL查询参数 |
Request |
表示HTTP请求 |
Response |
表示HTTP响应 |
Headers |
操作HTTP头 |
FormData |
创建和管理表单数据 |
AbortController |
取消异步操作 |
structuredClone() |
创建对象的深拷贝 |
示例:
const params = new URLSearchParams({ page: "1", size: "10" });
console.log(params.toString());
const copy = structuredClone({ name: "代码号", tags: ["node", "js"] });
copy.tags.push("global");
console.log(copy);
输出:
page=1&size=10
{ name: '代码号', tags: [ 'node', 'js', 'global' ] }
structuredClone()做深拷贝比JSON.parse(JSON.stringify(obj))更可靠,能处理Date、Map、Set等类型。
为什么不用JSON深拷贝:JSON.parse(JSON.stringify())会丢失undefined、函数、Date对象会变成字符串、循环引用直接报错。structuredClone()支持这些类型,还支持循环引用。
九、项目实例:跨模块共享配置
下面这段代码用全局对象和模块变量做一个轻量配置读取。
config.js
exports.load = () => ({
env: process.env.NODE_ENV || "development",
cwd: process.cwd(),
version: process.version
});
app.js
const config = require("./config").load();
console.log("运行环境:", config.env);
console.log("工作目录:", config.cwd);
console.log("Node 版本:", config.version);
运行NODE_ENV=productionnodeapp.js,输出类似:
运行环境: production
工作目录: /Users/alan/projects/demo
Node 版本: v22.21.1
这里用到了process.env、process.cwd()、process.version和require(),都是全局对象或模块级全局能力。这种写法适合脚本和小工具,项目规模大了之后建议换成专门的配置管理模块。
十、本节课程知识要点
-
global是Node.js根级对象,globalThis是跨环境标准写法,两者在Node.js中指向同一对象。 -
console写stdout/stderr,是调试和日志的基础入口。 -
process提供进程信息、环境变量、命令行参数和运行时控制。 -
Buffer处理原始二进制数据,文本场景优先用字符串。 -
定时器函数分同步、微任务、宏任务三个层次,执行顺序受事件循环阶段影响。
-
__dirname、__filename、exports、require是模块级全局变量,CommonJS和ESModule下行为不同。 -
fetch、URL、structuredClone等Web平台API已进入Node.js,使用前确认运行时版本。 -
挂到
global上的变量跨模块共享,但滥用会污染全局作用域,项目里优先用模块导出。