Node系列 · Node基础:全局变量与全局对象

Node系列 · Node基础:全局变量与全局对象

Node 运行时在 JS 标准全局对象之外,又注入了一批只在 Node 环境存在 的全局变量。它们不在 ECMAScript 规范里,浏览器看不到;记不清就很容易写出让本地能跑、生产报 ReferenceError 的代码。

一、Node 的全局对象分两层

层次 归属 在浏览器 在 Node
JS 标准全局 ECMAScript 规范 globalThisPromiseMapsetTimeout 同样存在
Node 专属全局 Node 运行时注入 不存在 globalprocessBuffer__dirname__filenameconsole

Node 中 globalThis === global(不同于浏览器里 globalThis === window):

javascript:global-check.js 复制代码
console.log(globalThis === global); // true

::: tip

判断代码运行环境的最小成本方法:typeof process !== 'undefined'

:::

二、定时器三件套:setTimeout / setInterval / setImmediate

2.1 三者的归属阶段

API 触发阶段 最小延迟
setTimeout(fn, 0) timers 1ms(HTML 规范,Node 同样遵守)
setInterval(fn, ms) timers 同上
setImmediate(fn) check 0(事件循环下一轮立即)

三者触发前都会先清空微任务队列,所以总是比 Promise.then 晚执行。

javascript:setTimer.js 复制代码
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));

console.log('sync');
bash 复制代码
$ node setTimer.js
sync
promise
timeout
immediate

执行顺序解释:

  1. sync ------ 同步代码立即执行
  2. promise ------ 微任务队列先于宏任务
  3. timeout ------ timers 阶段触发
  4. immediate ------ check 阶段触发,在 timers 之后

::: warning

不要把 setTimeout(fn, 0) 当成"立即执行"。它会被推迟到事件循环的 timers 阶段,且最小间隔是 1ms。要"尽快执行"且不阻塞当前同步流程,用 queueMicrotaskPromise.resolve().then()

:::

2.2 setTimeout / setInterval / setImmediate 的返回值与清除

三个定时器都有配套清除函数,返回值在 Node 里都是对象(Timeout / Timeout / Immediate),浏览器里是数字。返回值类型属于 Node 扩展行为,跨平台代码不要依赖,只用对应的 clearXxx 清除:

javascript:clear-timer.js 复制代码
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 秒:

javascript:setInterval-drift.js 复制代码
let n = 0;
const t = setInterval(() => {
  n++;
  console.log(`第 ${n} 次`, new Date().toISOString());
  if (n === 3) {
    clearInterval(t);
  }
}, 1000);

若想"上次结束 + N 毫秒"再触发,用递归 setTimeout

javascript:setInterval-self-correct.js 复制代码
function loop() {
  console.log(new Date().toISOString());
  setTimeout(loop, 1000);
}
loop();

三、console:不只是 log

Node 的 console 模块对外暴露 5 个基础方法 + 2 个格式化方法。按写入目标分组:

目标 方法 典型用途
stdout console.log / info / debug 普通日志、信息、调试
stderr console.warn / error 警告、错误(不会被正常日志吞掉)
stdout console.dir(obj, { depth }) 深打印对象
stdout console.table([...]) 打印数组/对象为表格
javascript:console-methods.js 复制代码
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  │
// └─────────┴─────┴─────┘

::: warning

console.log 在高并发下会阻塞事件循环 ------stdout 默认是 TTY 或管道,写入是同步调用。生产环境请用日志库(pino / winston / log4js)替代,必要时切换到异步写入。

:::

四、__dirname__filename

变量 类型
__dirname string 当前执行文件所在目录的绝对路径
__filename string 当前执行文件的绝对路径(含文件名)
javascript:paths.js 复制代码
console.log(__dirname);
// /Users/you/project/src
console.log(__filename);
// /Users/you/project/src/paths.js

使用场景:拼接配置文件、模板文件等相对路径,避免依赖 process.cwd()

javascript:read-config.js 复制代码
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"),这两个变量就不再存在,要这样取:

javascript:esm-dirname.mjs 复制代码
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

CommonJS 与 ESM 的完整对比会在模块化章节展开。

:::

五、Buffer:Node 处理二进制数据的核心

Buffer 是 Node 在 V8 堆之外分配的定长二进制缓冲区 。一句话:Buffer 实例 ≈ Uint8Array 的子类 + Node 专属 API。

5.1 创建方式

javascript:buffer-create.js 复制代码
// 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(默认)、hexbase64latin1ascii
javascript:buffer-encode.js 复制代码
const buf = Buffer.from('你好');
console.log(buf);
// <Buffer e4 bd a0 e5 a5 bd>
console.log(buf.toString('hex'));
// e4bda0e5a5bd
console.log(buf.toString('base64'));
// 5L2g5aW9

5.3 与 Uint8Array 的关系

javascript:buffer-uint8array.js 复制代码
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); // true

Buffer 实例可直接传给所有接受 Uint8Array 的 API(如 fs.readfetchbodyWebSocket.send)。

5.4 字符串长度 ≠ Buffer 长度

UTF-8 是变长编码:英文 1 字节、中文 3 字节、emoji 4 字节。Buffer.length字节数String.length字符数(按 UTF-16 code unit 计),两者不能互换:

javascript:buffer-slice.js 复制代码
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 模块解决这个问题。它知道字符边界,遇到不完整的字节序列会缓存起来,等下次写入再拼接:

javascript:string-decoder.js 复制代码
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 进程退出

javascript:process-exit.js 复制代码
process.exit();        // 同步退出,code = 0
process.exit(1);       // 失败退出,code = 1

::: warning

process.exit()立即终止进程 ,所有未完成的异步 IO、未刷新的写流都会被丢弃。HTTP 服务里应通过 server.close() 让现有请求跑完再退出:

javascript:graceful-shutdown.js 复制代码
process.on('SIGTERM', () => {
  server.close(() => process.exit(0));
});

:::

6.2 当前工作目录

process.cwd()运行时的工作目录 (启动命令所在的目录),而 __dirname 是当前模块文件所在目录------两者不同:

bash 复制代码
$ cd /home/you && node /opt/app/src/index.js
  • process.cwd()/home/you(启动时所在目录)
  • __dirname/opt/app/src(当前模块文件所在目录)

6.3 命令行参数

javascript:argv.js 复制代码
console.log(process.argv);
bash 复制代码
$ node argv.js --port 3000 config.json
[
  '/usr/local/bin/node',   // argv[0] node 可执行路径
  '/opt/app/src/argv.js',  // argv[1] 当前文件
  '--port',                // 用户参数从 argv[2] 开始
  '3000',
  'config.json'
]

实际项目里通常用 minimistcommander 解析,而不是手写 split。

6.4 平台与架构

javascript:platform.js 复制代码
console.log(process.platform); // 'darwin' | 'linux' | 'win32' | ...
console.log(process.arch);     // 'arm64' | 'x64' | ...

注意:process.platform 是 Node 二进制编译时的目标平台,不一定等于 OS 内核 。在 macOS 上跑 Rosetta 兼容层时 process.platform 仍是 'darwin'

6.5 进程间通信:发信号

javascript:kill.js 复制代码
process.kill(pid, 'SIGTERM');

process.kill 名字有歧义------它只是发送信号,不保证对方真的死。pid 不存在或没权限时抛异常。

Node 进程里常见的信号:

信号 来源 默认行为 进程该如何响应
SIGTERM kill <pid>、K8s/PM2 优雅停机 终止进程 监听后优雅退出 ------先 server.close()process.exit(0)
SIGINT Ctrl + C 终止进程 通常等同 SIGTERM,开发环境常用
SIGHUP 终端断开 / kill -HUP 终止进程 历史上用于"重新加载配置";现在多用作自定义信号
SIGKILL kill -9 立即终止,不可拦截 监听不到------OS 直接杀进程
SIGUSR1 / SIGUSR2 用户自定义 终止进程 Node 默认不做任何事,可作自定义触发

K8s pod 终止流程:

  1. K8s 发 SIGTERM
  2. pod 进入 terminationGracePeriodSeconds(默认 30s)倒计时
  3. 超时后 K8s 发 SIGKILL 强杀

所以 Node 进程必须监听 SIGTERM 并优雅退出,否则正在处理的请求会被中途切断。

6.6 环境变量

javascript:env.js 复制代码
console.log(process.env.NODE_ENV); // 'development' | 'production' | undefined

读取自定义变量时,|| 提供本地开发默认值是常见做法:

javascript:env-default.js 复制代码
const dbUrl = process.env.DATABASE_URL || 'mongodb://localhost:27017/dev';

::: tip

不要把 .env 文件直接 require 进生产 。使用 dotenvnode --env-file=.env(Node 20.6+ 已内置)按环境注入。

:::

6.7 标准 IO

用途
process.stdin 标准输入(可读)
process.stdout 标准输出(可写)
process.stderr 标准错误(可写)
javascript:stdin-echo.js 复制代码
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 路径 当前模块路径(仅 CJS)
Buffer 二进制 处理字节流
process 进程 环境、退出、信号、参数

表中仅列前文展开过的全局对象。Node 还提供 queueMicrotaskURLTextEncoder/TextDecoderfetch(Node 18+)等,本文不展开。

八、小结

  • Node 全局对象分 ECMAScript 标准层 + Node 专属层,后者浏览器看不到
  • 定时器三件套各属不同阶段,理解阶段顺序就理解执行顺序
  • __dirname / __filename 只在 CJS 存在;ESM 用 import.meta.url + fileURLToPath
  • Buffer 是 Node 的二进制基石,等价于 Uint8Array 子类
  • process 几乎覆盖了进程的所有可观察状态与可控行为
相关推荐
码云骑士1 小时前
104-实战论文搜索引擎-ArXiv爬取-Milvus存储-RAG问答-Gradio前端
前端·python·搜索引擎·milvus
sunly_1 小时前
TypeScript总结:15、类型速查
前端·javascript·typescript
Brown.alexis1 小时前
es6知识点3-自备使用
前端·javascript·es6
2601_953988071 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·后端·物联网·tcp/ip·数学建模·前端框架
天空之城--1 小时前
Claude Code 高效开发 Web 2D/3D 完全指南:心法、自定义 Skill 体系与社区技能包实战
前端·3d
breeze jiang1 小时前
Next.js App Router 父子组件 RSC 拆分实战:从 Redis Hash 到客户端交互的最小化边界
javascript·redis·哈希算法
IT_陈寒1 小时前
SpringBoot自动配置坑了我三天,原来漏了这个注解
前端·人工智能·后端
丫头,冲鸭!!!1 小时前
记账网站-post功能
javascript·个人开发
鬼手点金1 小时前
Scrapy + Playwright 完整示例(JS 动态渲染网页)
开发语言·javascript·爬虫·python·scrapy·html·json