Node系列 · Node基础全局变量与全局对象Node 运行时在 JS 标准全局对象之外又注入了一批只在 Node 环境存在的全局变量。它们不在 ECMAScript 规范里浏览器看不到记不清就很容易写出让本地能跑、生产报ReferenceError的代码。一、Node 的全局对象分两层层次归属在浏览器在 NodeJS 标准全局ECMAScript 规范globalThis、Promise、Map、setTimeout等同样存在Node 专属全局Node 运行时注入不存在global、process、Buffer、__dirname、__filename、console等Node 中globalThis global不同于浏览器里globalThis windowconsole.log(globalThis global); // true::: tip判断代码运行环境的最小成本方法typeof process ! undefined。:::二、定时器三件套setTimeout/setInterval/setImmediate2.1 三者的归属阶段API触发阶段最小延迟setTimeout(fn, 0)timers1msHTML 规范Node 同样遵守setInterval(fn, ms)timers同上setImmediate(fn)check0事件循环下一轮立即三者触发前都会先清空微任务队列所以总是比Promise.then晚执行。setTimeout(() console.log(timeout), 0); setImmediate(() console.log(immediate)); Promise.resolve().then(() console.log(promise)); console.log(sync);$nodesetTimer.jssyncpromisetimeoutimmediate执行顺序解释sync—— 同步代码立即执行promise—— 微任务队列先于宏任务timeout—— timers 阶段触发immediate—— check 阶段触发在 timers 之后::: warning不要把setTimeout(fn, 0)当成立即执行。它会被推迟到事件循环的 timers 阶段且最小间隔是 1ms。要尽快执行且不阻塞当前同步流程用queueMicrotask或Promise.resolve().then()。:::2.2setTimeout/setInterval/setImmediate的返回值与清除三个定时器都有配套清除函数返回值在 Node 里都是对象Timeout/Timeout/Immediate浏览器里是数字。返回值类型属于 Node 扩展行为跨平台代码不要依赖只用对应的clearXxx清除const t1 setTimeout(fn, 1000); const t2 setInterval(fn, 1000); const t3 setImmediate(fn); clearTimeout(t1); clearInterval(t2); clearImmediate(t3);清除时机未触发的回调调用clearXxx后回调不再执行——这是常规用法已触发的回调清除无效回调已经跑完或正在跑重复触发仅setInterval清除后停止排队下一次已经在 poll 队列里的本次执行无法阻止setInterval的失真setInterval(fn, 1000)不保证每 1 秒精确执行。如果回调本身耗时 800ms下一次触发会在回调结束后立即排队而不是和上一次间隔 1 秒let n 0; const t setInterval(() { n; console.log(第 ${n} 次, new Date().toISOString()); if (n 3) { clearInterval(t); } }, 1000);若想上次结束 N 毫秒再触发用递归setTimeoutfunction loop() { console.log(new Date().toISOString()); setTimeout(loop, 1000); } loop();三、console不只是logNode 的console模块对外暴露 5 个基础方法 2 个格式化方法。按写入目标分组目标方法典型用途stdoutconsole.log/info/debug普通日志、信息、调试stderrconsole.warn/error警告、错误不会被正常日志吞掉stdoutconsole.dir(obj, { depth })深打印对象stdoutconsole.table([...])打印数组/对象为表格console.dir({ a: { b: { c: 1 } } }, { depth: 4 }); // { a: { b: { c: 1 } } } console.table([{ name: A, age: 1 }, { name: B, age: 2 }]); // ┌─────────┬─────┐ // │ (index) │ name │ age │ // ├─────────┼─────┼─────┤ // │ 0 │ A │ 1 │ // │ 1 │ B │ 2 │ // └─────────┴─────┴─────┘::: warningconsole.log在高并发下会阻塞事件循环——stdout 默认是 TTY 或管道写入是同步调用。生产环境请用日志库pino / winston / log4js替代必要时切换到异步写入。:::四、__dirname与__filename变量类型值__dirnamestring当前执行文件所在目录的绝对路径__filenamestring当前执行文件的绝对路径含文件名console.log(__dirname); // /Users/you/project/src console.log(__filename); // /Users/you/project/src/paths.js使用场景拼接配置文件、模板文件等相对路径避免依赖process.cwd()。const fs require(fs); const path require(path); const configPath path.join(__dirname, config.json); const config JSON.parse(fs.readFileSync(configPath, utf-8));::: warning__dirname与__filename仅 CommonJS 模块可用。Node 加载 JS 文件时默认按 CommonJS简称 CJS规范处理五个核心参数exports / require / module / __filename / __dirname由 Node 注入。如果项目改成 ESM.mjs后缀或在package.json设type: module这两个变量就不再存在要这样取import { fileURLToPath } from node:url; import { dirname } from node:path; const __filename fileURLToPath(import.meta.url); const __dirname dirname(__filename);CommonJS 与 ESM 的完整对比会在模块化章节展开。:::五、BufferNode 处理二进制数据的核心Buffer是 Node 在 V8 堆之外分配的定长二进制缓冲区。一句话Buffer实例 ≈Uint8Array的子类 Node 专属 API。5.1 创建方式// 1. 指定字节数未初始化可能包含旧内存数据 const buf1 Buffer.alloc(4); // 00000000 00000000 00000000 00000000 // 2. 指定字节数 填充值 const buf2 Buffer.alloc(4, 0x41); // AAAA // 3. 从字符串创建默认 UTF-8 const buf3 Buffer.from(hello); // Buffer 68 65 6c 6c 6f // 4. 从数组创建 const buf4 Buffer.from([0x68, 0x65]); // Buffer 68 65::: warning永远不要用new Buffer(...)。Buffer构造函数已被弃用且未初始化内存会泄漏数据。统一用Buffer.alloc/Buffer.from。:::5.2 字节范围与字符编码每个字节取值0-255即0x00 - 0xFF常用编码utf-8默认、hex、base64、latin1、asciiconst buf Buffer.from(你好); console.log(buf); // Buffer e4 bd a0 e5 a5 bd console.log(buf.toString(hex)); // e4bda0e5a5bd console.log(buf.toString(base64)); // 5L2g5aW95.3 与Uint8Array的关系const u8 new Uint8Array([1, 2, 3]); const buf Buffer.from(u8); console.log(buf instanceof Uint8Array); // true console.log(Buffer.prototype.__proto__ Uint8Array.prototype); // trueBuffer实例可直接传给所有接受Uint8Array的 API如fs.read、fetch的body、WebSocket.send。5.4 字符串长度 ≠ Buffer 长度UTF-8 是变长编码英文 1 字节、中文 3 字节、emoji 4 字节。Buffer.length是字节数String.length是字符数按 UTF-16 code unit 计两者不能互换const buf Buffer.from(你好世界); console.log(buf.length); // 12 字节4 × 3 console.log(buf.toString().length); // 4 字符 // 错误从中间字节切开会乱码 console.log(buf.slice(0, 5).toString()); // 你?后半截被截断Node 提供string_decoder模块解决这个问题。它知道字符边界遇到不完整的字节序列会缓存起来等下次写入再拼接const { StringDecoder } require(string_decoder); const decoder new StringDecoder(utf8); // 第一次写入拿到一个完整字符缓存为空 console.log(decoder.write(buf.slice(0, 3))); // 你 // 第二次写入跨字节切片2 3 5 字节decoder 自动补完 console.log(decoder.write(buf.slice(3, 8))); // 好世 // 拿回缓存里残留的不完整字符 console.log(decoder.end()); // 界这个模块在处理流式 IO如net/fs的 data 事件时几乎是必备——单次 data 回调拿到的字节切片不会保证按字符边界对齐自己直接toString()必然乱码。六、process进程级控制中心process是 Node 运行时暴露的当前进程对象属性与方法按退出、参数、环境、信号、子进程、IO分组。6.1 进程退出process.exit(); // 同步退出code 0 process.exit(1); // 失败退出code 1::: warningprocess.exit()会立即终止进程所有未完成的异步 IO、未刷新的写流都会被丢弃。HTTP 服务里应通过server.close()让现有请求跑完再退出process.on(SIGTERM, () { server.close(() process.exit(0)); });:::6.2 当前工作目录process.cwd()是运行时的工作目录启动命令所在的目录而__dirname是当前模块文件所在目录——两者不同$cd/home/younode/opt/app/src/index.jsprocess.cwd()→/home/you启动时所在目录__dirname→/opt/app/src当前模块文件所在目录6.3 命令行参数console.log(process.argv);$nodeargv.js--port3000config.json[/usr/local/bin/node, // argv[0]node可执行路径/opt/app/src/argv.js, // argv[1]当前文件--port, // 用户参数从 argv[2]开始3000,config.json]实际项目里通常用minimist或commander解析而不是手写 split。6.4 平台与架构console.log(process.platform); // darwin | linux | win32 | ... console.log(process.arch); // arm64 | x64 | ...注意process.platform是 Node 二进制编译时的目标平台不一定等于 OS 内核。在 macOS 上跑 Rosetta 兼容层时process.platform仍是darwin。6.5 进程间通信发信号process.kill(pid, SIGTERM);process.kill名字有歧义——它只是发送信号不保证对方真的死。pid不存在或没权限时抛异常。Node 进程里常见的信号信号来源默认行为进程该如何响应SIGTERMkill pid、K8s/PM2 优雅停机终止进程监听后优雅退出——先server.close()再process.exit(0)SIGINTCtrl C终止进程通常等同 SIGTERM开发环境常用SIGHUP终端断开 /kill -HUP终止进程历史上用于重新加载配置现在多用作自定义信号SIGKILLkill -9立即终止不可拦截监听不到——OS 直接杀进程SIGUSR1/SIGUSR2用户自定义终止进程Node 默认不做任何事可作自定义触发K8s pod 终止流程K8s 发SIGTERMpod 进入terminationGracePeriodSeconds默认 30s倒计时超时后 K8s 发SIGKILL强杀所以Node 进程必须监听SIGTERM并优雅退出否则正在处理的请求会被中途切断。6.6 环境变量console.log(process.env.NODE_ENV); // development | production | undefined读取自定义变量时||提供本地开发默认值是常见做法const dbUrl process.env.DATABASE_URL || mongodb://localhost:27017/dev;::: tip不要把.env文件直接require进生产。使用dotenv或node --env-file.envNode 20.6 已内置按环境注入。:::6.7 标准 IO流用途process.stdin标准输入可读process.stdout标准输出可写process.stderr标准错误可写process.stdin.setEncoding(utf8); process.stdin.on(data, (chunk) { process.stdout.write(echo: ${chunk}); });七、对比表本文涉及的全局对象一览名称类别Node 专属典型用途global/globalThis全局对象否标准跨环境统一访问全局setTimeout/clearTimeout定时器否标准延迟执行setInterval/clearInterval定时器否标准周期性执行setImmediate/clearImmediate定时器是事件循环下一轮立即console日志否Web 也有调试输出__dirname/__filename路径是当前模块路径仅 CJSBuffer二进制是处理字节流process进程是环境、退出、信号、参数表中仅列前文展开过的全局对象。Node 还提供queueMicrotask、URL、TextEncoder/TextDecoder、fetchNode 18等本文不展开。八、小结Node 全局对象分 ECMAScript 标准层 Node 专属层后者浏览器看不到定时器三件套各属不同阶段理解阶段顺序就理解执行顺序__dirname/__filename只在 CJS 存在ESM 用import.meta.urlfileURLToPathBuffer是 Node 的二进制基石等价于Uint8Array子类process几乎覆盖了进程的所有可观察状态与可控行为