CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Node.js 后端入门/课时/运行时与 process
RawBlame
课时目录12 节

Node.js 后端入门

  1. 01Node.js 是什么
  2. 02运行时与 process
  3. 03模块与 npm
  4. 04文件系统与 path
  5. 05http 模块
  6. 06Express 入门
  7. 07事件循环与异步
  8. 08环境变量与配置
  9. 09Streams 基础
  10. 10错误处理
  11. 11测试 node:test
  12. 12综合练习与选型

运行时与 process

运行脚本、process.cwd、stdin/stdout、退出码。

30 分钟

运行时与 process

这一课你将能做到

  1. 熟练使用 process.argv、process.cwd()、process.env 编写接收参数的命令行脚本
  2. 用正确的退出码结束脚本(process.exit),让脚本可以被 CI 和其他程序判断成败
  3. 使用 process.stdout / process.stderr / process.stdin 与管道、重定向配合工作

为什么要学

process 是 Node 给每个进程准备的「全局控制台」:命令行参数、环境变量、标准输入输出、退出码全在这里。日常最有感的三类场景都靠它:

  • 写脚本:内容批处理、数据导入,第一件事就是解析命令行参数;
  • 接配置:数据库地址、密钥都从 process.env 读(第 8 课展开);
  • 接 CI/CD:脚本退出码非 0,流水线立刻标红——「成功静默、失败喊叫」就靠它。

人话:process 就是脚本和操作系统之间唯一的对话窗口,这课学会它,你写的就不只是「能跑的代码」,而是「能被指挥、能报状态」的工具。

先建立图像

你在终端输入:node greet.js --name Java
                │
                ▼
┌────────────────── process ──────────────────┐
│ argv      = [node路径, greet.js路径, --name, Java]
│ env       = { PATH: ..., HOME: ..., ... }    ← 系统环境变量
│ cwd()     = h:\work\java-nav                 ← 终端当前目录
│ platform  = win32
│ stdout    ──► 终端(可被 | 管道接走)
│ stderr    ──► 终端(错误专用通道)
│ exit(code)──► 0 成功 / 非 0 失败
└─────────────────────────────────────────────┘

记住一条主线:argv 管「指挥」,env 管「配置」,stdout/stderr 管「汇报」,exit 码管「成败」。

跟着做

步骤 1:解析命令行参数

新建文件夹 node-process,npm init -y 后新建 greet.js:

// greet.js
// node greet.js Java
const args = process.argv.slice(2);   // 去掉前两项,只留用户参数
const name = args[0] ?? 'stranger';
console.log(`你好,${name}!`);

运行:

node greet.js
# 你好,stranger!

node greet.js Java
# 你好,Java!

slice(2) 是最常见的写法:argv 第 0 项是 node 可执行文件路径,第 1 项是脚本路径,真正传给你的从下标 2 开始。

步骤 2:支持 --key=value 风格参数

裸数组在参数一多就难维护。写一个极简解析器(不装任何依赖):

// args.js —— 解析 --key=value 和 --flag 形式
function parseArgs(argv) {
  const parsed = {};
  for (const arg of argv) {
    if (arg.startsWith('--')) {
      const eq = arg.indexOf('=');
      if (eq === -1) {
        parsed[arg.slice(2)] = true;              // --verbose 之类开关
      } else {
        parsed[arg.slice(2, eq)] = arg.slice(eq + 1); // --name=Java
      }
    }
  }
  return parsed;
}

module.exports = { parseArgs };

在 greet.js 里改用它:

// greet.js(升级版)
const { parseArgs } = require('./args');

const args = parseArgs(process.argv.slice(2));
const name = args.name ?? 'stranger';

if (args.verbose) {
  console.error(`[verbose] 收到参数:`, args);
}
console.log(`你好,${name}!`);
node greet.js --name=Java --verbose
# [verbose] 收到参数: { name: 'Java', verbose: true }
# 你好,Java!

参数一多,这种 --key=value 风格比按下标取值健壮得多。需求更复杂时再换官方推荐的 util.parseArgs()。

步骤 3:cwd 与脚本目录——新手第一大坑

新建 tools/where.js:

// tools/where.js
const path = require('node:path');

console.log('process.cwd()   =', process.cwd());
console.log('__dirname       =', __dirname);
console.log('两者相同吗      =', process.cwd() === __dirname);

在项目根目录运行:

node tools/where.js
# process.cwd()   = h:\work\node-process
# __dirname       = h:\work\node-process\tools
# 两者相同吗      = false

结论:

  • process.cwd():终端当前所在目录,相对路径都相对它;
  • __dirname:脚本文件所在目录,永远稳定。

所以「读脚本旁边的配置文件」必须用 __dirname 拼路径,用相对路径等于把脚本命运交给了调用者的终端位置。ESM 里没有 __dirname,用 path.dirname(fileURLToPath(import.meta.url)) 等价替代(第 3 课讲 ESM)。

步骤 4:退出码——成功静默,失败喊叫

新建 check.js,模拟一个校验任务:

// check.js
const fs = require('node:fs');

const file = process.argv[2];

if (!file) {
  console.error('用法: node check.js <文件路径>');
  process.exit(1);            // 参数缺失:退出码 1
}

if (!fs.existsSync(file)) {
  console.error(`文件不存在: ${file}`);
  process.exit(2);            // 找不到文件:退出码 2
}

console.log(`校验通过: ${file}`);
// 不调用 process.exit 时,事件循环空了自然退出,退出码 0

运行并查看退出码:

node check.js note.txt
# 校验通过: note.txt
echo $LASTEXITCODE      # PowerShell 查看上一条命令退出码,输出 0

node check.js
# 用法: node check.js <文件路径>
echo $LASTEXITCODE      # 输出 1

约定俗成:0 成功,1 一般失败,其他数字自定义错误类别。CI 流水线、&& 串联命令、进程管理器都靠退出码判断脚本死活。

步骤 5:stdout / stderr 分流

console.log 其实写到 process.stdout,console.error 写到 process.stderr。两条通道分开后,错误信息才不会被「正常输出」淹没:

node check.js note.txt 2> err.log
# 正常输出仍在屏幕,错误进了 err.log

node greet.js --name=Java | node args-demo.js   # stdout 可以接进下一个程序

先看一个 stdin 的小交互,新建 ask.js:

// ask.js
process.stdout.write('你的名字?');
process.stdin.setEncoding('utf8');

process.stdin.on('data', (chunk) => {
  const name = chunk.trim() || 'stranger';
  console.log(`你好,${name}!`);
  process.stdin.pause();     // 停止监听,让事件循环空下来从而退出
});
node ask.js
# 你的名字?Java
# 你好,Java!

日常 90% 场景用 console.log/error 就够了,但知道底层是两条流,排障时(比如「日志怎么进文件了」)就有抓手。

步骤 6:几个常用的 process 信息

新建 info.js:

// info.js
console.log('Node 版本   :', process.version);
console.log('平台        :', process.platform);        // win32 / linux / darwin
console.log('CPU 架构    :', process.arch);           // x64 / arm64
console.log('运行时长(s) :', Math.round(process.uptime()));
console.log('内存占用    :', Math.round(process.memoryUsage().heapUsed / 1024 / 1024), 'MB');
node info.js
# Node 版本   : v20.11.0
# 平台        : win32
# CPU 架构    : x64
# 运行时长(s) : 0
# 内存占用    : 4 MB

写跨平台脚本时用 process.platform 分支(比如 Windows 换行符是 \r\n),比硬编码可靠。

讲清楚要点

  1. argv 前 2 项不是你的参数:[node路径, 脚本路径, ...用户参数],处理前先 slice(2)。
  2. 相对路径参照 cwd,不参照脚本:fs.readFileSync('a.txt') 读的是终端当前目录下的 a.txt。要锚定脚本位置用 __dirname(CJS)或 import.meta.url(ESM)。
  3. 退出码是脚本的「状态汇报协议」:0 成功、非 0 失败。被 CI/流水线调用的脚本必须认真设计退出码,否则「静默失败」比崩溃更危险。
  4. stdout 走数据、stderr 走错误:console.log → stdout,console.error → stderr。分流后才能 2> 单独收集错误日志。
  5. process.env 里的值全是字符串:PORT=0 读出来是 "0",做数字比较前要 Number() 转换——这是经典 bug 源。

你可能会懵的地方

  • process.exit(1) 和脚本自然结束有什么区别? 自然结束是事件循环里的任务跑完了,退出码 0。process.exit() 立刻终止进程——未写完的异步 I/O 会被打断(比如刚发起的文件写入可能没落盘)。只在明确要报错时用它;正常收尾用 process.exitCode = 1 更安全,它等事件循环自然排空后再退出。

  • 环境变量和命令行参数都用,怎么选? 参数表达「这次运行做什么」(输入文件、目标环境),环境变量表达「这个环境里有什么」(密钥、地址)。频繁变的走参数,按部署环境固定的走 env。

  • --verbose 打出来的日志为什么用 console.error? 调试信息本质是「旁路信息」,进 stderr 才不会污染 stdout 的数据流,管道下游拿到的才是纯数据。

  • Windows 和 Linux 的 PATH 分隔符一样吗? 不一样:Windows 是 ;,Linux/macOS 是 :。需要解析时用 path.delimiter,别硬编码。

  • 怎么看上一条命令的退出码? PowerShell 用 echo $LASTEXITCODE,cmd 用 echo %errorlevel%,bash 用 echo $?。

错法对照

错法问题正解
process.argv[2] 直接当文件名,不判空没传参时 undefined 引发下游诡异报错先判空并输出用法、process.exit(1)
用相对路径读脚本同目录文件换个目录运行就 ENOENTpath.join(__dirname, 'a.txt')
失败时只 console.log 不设置退出码CI 认为 0 成功,坏数据悄悄上线console.error + process.exit(1) 或 process.exitCode = 1
process.exit() 后还指望异步任务完成进程已死,未落盘用 process.exitCode,或把退出放在流 finish 回调后
Number(process.env.PORT) === 0 写成 process.env.PORT === 0env 是字符串 "0",永不相等显式 Number() 转换

卡点急救

  1. 脚本在 A 目录正常、在 B 目录报 ENOENT:相对路径踩了 cwd 坑,改用 __dirname 拼绝对路径。
  2. process.exit() 后日志没打出来:stdout 是异步的,exit 截断了输出。排查场景改用 process.exitCode,或对 stdout 监听 drain/用同步写。
  3. PowerShell 里 node a.js && node b.js 报错:旧版 PowerShell 不支持 &&,用 node a.js; if ($LASTEXITCODE -eq 0) { node b.js },或换 PowerShell 7+。
  4. stdin 交互脚本不退出:stdin 监听器让事件循环一直有活干,处理完记得 process.stdin.pause() 或 process.exit()。

面试常问

  • process.argv 是什么结构? 数组:[node 可执行文件路径, 当前脚本路径, ...命令行参数]。实际开发用 slice(2) 取业务参数,复杂场景用 util.parseArgs 或 commander 这类库。

  • process.cwd() 和 __dirname 的区别? cwd 是进程启动时终端所在目录,随调用位置变化;__dirname 是脚本文件自身所在目录,恒定。读「脚本旁边」的文件必须用 __dirname。

  • process.exit() 的坑? 立即终止进程,stdout/stderr 的异步写入和进行中的 I/O 可能被截断。推荐非致命场景用 process.exitCode 设置退出码让进程自然结束,致命错误才 exit()。

  • Node 进程退出码 0 和非 0 意味着什么? 0 表示成功;非 0 表示失败,具体数值可自定义错误类别。CI、进程管理器、shell 的 && 都依据退出码决定后续行为。

自测三问

  1. node greet.js --name=Java 时 process.argv 数组有几个元素?分别是什么?
  2. 在 h:\work 目录执行 node tools/where.js,此时 process.cwd() 和 __dirname 各是什么?
  3. 为什么错误信息要打给 stderr 而不是 stdout?

小结一句话

process 是脚本与操作系统之间的控制台:argv 接指挥、env 读配置、stdout/stderr 分流汇报、退出码宣告成败——掌握它,脚本才算「可被指挥、可报状态」。

延伸

  • Node.js 官方文档 process
  • 下一课:模块与 npm——CommonJS 和 ESM 怎么选,package.json 与 node_modules 的运行法则。
加载本节练习…
← 上一课Node.js 是什么下一课 →模块与 npm