第2章 2.3 os 与 process 模块

⏱️ 学习节奏:90分钟
🎯 难度:入门 → 进阶


上一章我们折腾了 path 模块,把文件路径玩得滚瓜烂熟。学到这儿你应该有个感受:Node.js 就像一个超级瑞士军刀,自带工具特别全。

但你有没有想过:Node.js 自己是运行在什么环境里的?电脑是 Mac 还是 Windows?有多少个 CPU 核心?能查看内存使用情况吗?

这些问题听起来像是「系统管理员」才关心的,但做项目时你迟早会遇到:
- 你的网站要判断用户是手机还是电脑访问
- 你要读取环境变量(数据库密码、API密钥)不想写死在代码里
- 你写的脚本要在不同操作系统上都能跑

这一章,我们就来认识 Node.js 自带的「透视眼」—— os 模块和 process 对象。

学完之后,你就能写出「知道自己运行在什么环境」的智能代码了。


🎯 开场 3 分钟:为什么要学这个?

场景还原

想象你开了个快递站:

  • os 模块就像是告诉你快递站的基本情况:仓库有多大(内存)、有多少个打包台(CPU核心)、是在工业区还是商业区(操作系统)
  • process 模块就像是告诉你当前正在处理的这个快递:是谁寄的(当前工作目录)、要不要加急(进程信息)、怎么签名签收(标准输入输出)

你做快递站老板,难道不需要知道这些信息吗?

痛点问题

痛点1:代码写死了 Windows 路径 C:\Users\admin\file.txt,到 Mac 上直接报错
痛点2:API 密钥直接写代码里,一提交 Git 就泄露了
痛点3:不知道程序占了多少内存,服务器突然崩了都不知道为啥

解决方案

✅ 学完这章你能:
- 写出跨平台兼容的代码(Windows/Mac/Linux 通吃)
- 安全管理环境变量(敏感信息不进代码)
- 监控资源使用,提前发现问题


🧱 基础 25 分钟:核心概念

概念1:os 模块 —— 透视你的电脑

os 是 "operating system"(操作系统)的缩写。把它想象成 Node.js 派去侦查你电脑情况的侦察兵

引入方式

const os = require('os');

就一句话,Node.js 自带的,不用安装。


1.1 查看电脑基本信息

查看操作系统 + 平台

console.log('操作系统:', os.platform());  // 'win32' / 'darwin' / 'linux'
console.log('详细类型:', os.type());       // 'Windows_NT' / 'Darwin' / 'Linux'
console.log('架构:', os.arch());           // 'x64' / 'arm64'

输出示例(Mac电脑):

操作系统: darwin
详细类型: Darwin
架构: x64

💡 这三行代码就像问电脑「你是谁」——platform() 告诉你大类别,type() 告诉你具体名字,arch() 告诉你 CPU 是几位的。

查看 CPU 核心数

console.log('CPU 核心数:', os.cpus().length);

输出:

CPU 核心数: 8

💡 核心数越多,同时处理的任务越多。就像快递站打包台越多,同时能处理的包裹就越多。

查看内存情况

const totalMem = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2);  // 总内存 GB
const freeMem = (os.freemem() / 1024 / 1024 / 1024).toFixed(2);      // 空闲内存 GB

console.log(`总内存:${totalMem} GB`);
console.log(`空闲内存:${freeMem} GB`);

输出:

总内存:16.00 GB
空闲内存:8.50 GB

配图1 - 配图1

💡 totalmem() 返回的是字节数,所以我们除以三次 1024 转成 GB。注意!这是物理内存,不是硬盘!


1.2 查看用户主目录和临时目录

console.log('用户主目录:', os.homedir());
console.log('临时文件目录:', os.tmpdir());

输出示例(Mac):

用户主目录: /Users/apple
临时文件目录: /var/folders/xx/.../T

💡 homedir() 常用来拼接用户相关的配置路径,tmpdir() 用来存临时文件。


1.3 查看网络地址

console.log('本机IP地址:', os.networkInterfaces());

这会输出网卡的详细信息,包括 IP 地址、MAC 地址等。

💡 网络接口可能有好几个(WiFi、以太网、蓝牙等),所以返回的是个对象,不是单个值。


概念2:process 对象 —— 透视正在运行的程序

如果说 os 是侦察电脑情况的,那 process 就是侦察当前这个 Node.js 程序的。

process 是个全局对象,不需要 require,随时都能用。


2.1 查看当前工作目录

console.log('当前工作目录:', process.cwd());

输出:

当前工作目录: /Users/apple/workspace/my-project

💡 cwd() = "current working directory"。就像你站在快递站的哪个位置,这个告诉你当前程序在哪个文件夹运行。


2.2 查看环境变量

环境变量就是程序的「外挂配置」。就像快递站可以外挂一个「是否加急处理」的开关。

console.log('所有环境变量:', process.env);

这会输出一个超大的对象,包含 PATH、USER、SHELL 等系统配置。

重点:读取单个变量

const nodePath = process.env.PATH;       // 系统的PATH变量
const myConfig = process.env.MY_CONFIG;   // 自定义的环境变量(可以是任何名字)

console.log('PATH路径:', nodePath);
console.log('我的配置:', myConfig);  // 可能是 undefined(没设置的话)

💡 process.env 是个大字典,你想读什么就 process.env.你想要的变量名。注意!没设置的变量返回 undefined,不会报错。


2.3 设置环境变量(重要!)

process.env.MY_SECRET = 'abc123';  // 设置一个环境变量

console.log('我的秘密:', process.env.MY_SECRET);  // abc123

💡 实战用途:数据库密码、API 密钥都可以这样放环境变量里,代码里不出现明文!


2.4 查看进程信息

console.log('进程ID:', process.pid);
console.log('进程运行时间(秒):', process.uptime().toFixed(2));
console.log('Node版本:', process.version);
console.log('V8引擎版本:', process.versions.v8);

输出:

进程ID: 12345
进程运行时间(秒): 12.34
Node版本: v20.10.0
V8引擎版本: 11.0.123

💡 这些信息在做性能监控和调试时特别有用。比如 uptime() 可以监控程序运行了多久。


2.5 标准输入输出

这个是面试常问的!但理解起来很简单:

  • process.stdin = 键盘输入(标准输入)
  • process.stdout = 屏幕输出(标准输出)
  • process.stderr = 错误输出(标准错误)
// 简单理解:
process.stdout.write('Hello World\n');  // 等价于 console.log

// 监听用户输入:
process.stdin.on('data', (input) => {
console.log('你输入了:', input.toString().trim());
});

配图2 - 配图2


2.6 进程退出

process.exit(0);  // 正常退出
process.exit(1);  // 异常退出(带着错误码)

⚠️ exit(0) 表示程序圆满完成,exit(1) 表示出了错误。一般在 CLI 工具里会用到。


2.7 信号处理(进阶)

当你按 Ctrl + C 终止程序时,Node.js 会收到一个「信号」。

process.on('SIGINT', () => {
console.log('收到中断信号,正在优雅退出...');
process.exit(0);
});

💡 想象快递站收到「系统维护」通知,要先把手头的单子处理完再关机。这就是「优雅退出」。


🔥 实战 35 分钟:3 个递进的小项目

项目1(5分钟):我的电脑信息面板

需求:把当前电脑的关键信息展示出来。

完整代码:

// project1_pc_info.js
const os = require('os');

function formatBytes(bytes) {
const gb = (bytes / 1024 / 1024 / 1024).toFixed(2);
return `${gb} GB`;
}

console.log('═══════════════════════════════════');
console.log('        📊 我的电脑信息面板        ');
console.log('═══════════════════════════════════');
console.log(`操作系统:${os.type()} (${os.platform()})`);
console.log(`CPU架构:${os.arch()}`);
console.log(`CPU核心数:${os.cpus().length} 核`);
console.log(`总内存:${formatBytes(os.totalmem())}`);
console.log(`空闲内存:${formatBytes(os.freemem())}`);
console.log(`用户目录:${os.homedir()}`);
console.log(`当前时间:${new Date().toLocaleString()}`);
console.log('═══════════════════════════════════');

预期输出:

═══════════════════════════════════
    📊 我的电脑信息面板        
═══════════════════════════════════
操作系统:Darwin (darwin)
CPU架构:x64
CPU核心数:8 核
总内存:16.00 GB
空闲内存:8.50 GB
用户目录:/Users/apple
当前时间:2024/1/15 14:30:00
═══════════════════════════════════

💡 这个项目让你熟悉 os 模块的基本用法。formatBytes 是个小工具函数,把字节转成 GB。


项目2(15分钟):跨平台路径处理工具

需求:做一个自动拼接路径的小工具,能在不同操作系统上正常工作。

完整代码:

// project2_path_helper.js
const os = require('os');
const path = require('path');

// 模拟一些配置
const config = {
userName: process.env.USER || 'guest',  // 从环境变量读用户名,没设置就用guest
projectName: 'my-awesome-project'
};

function getProjectPath() {
// 跨平台的路径拼接
const basePath = os.homedir();           // 用户主目录
const projectPath = path.join(
basePath,
'Documents',                           // Documents文件夹
'projects',                            
config.projectName
);
return projectPath;
}

function getLogPath() {
return path.join(getProjectPath(), 'logs');
}

function getConfigPath() {
return path.join(getProjectPath(), 'config.json');
}

// 输出结果
console.log('👤 用户:', config.userName);
console.log('📁 项目路径:', getProjectPath());
console.log('📝 日志路径:', getLogPath());
console.log('⚙️  配置路径:', getConfigPath());

// 验证路径格式(根据系统不同)
const isWindows = os.platform() === 'win32';
console.log('\n🔍 系统检测:', isWindows ? 'Windows' : 'Mac/Linux');
console.log('路径分隔符:', path.sep);

预期输出(Mac):

👤 用户: apple
📁 项目路径: /Users/apple/Documents/projects/my-awesome-project
📝 日志路径: /Users/apple/Documents/projects/my-awesome-project/logs
⚙️  配置路径: /Users/apple/Documents/projects/my-awesome-project/config.json

🔍 系统检测: Mac/Linux
路径分隔符: /

预期输出(Windows):

👤 用户: admin
📁 项目路径: C:\Users\admin\Documents\projects\my-awesome-project
...

💡 重点:用了 path.join() 而不是字符串拼接!这样不管在哪个系统上都能正确处理路径分隔符。


项目3(15分钟):环境变量管理的待办清单工具

需求:做一个命令行待办清单,数据存在用户目录下,环境变量控制是否显示「完成时间」。

完整代码:

// project3_todo_list.js
const os = require('os');
const fs = require('fs');
const path = require('path');

// ==================== 配置 ====================
const SHOW_TIMESTAMP = process.env.SHOW_TIMESTAMP === 'true';  // 环境变量控制
const TODO_FILE = path.join(os.homedir(), '.my-todos.json');

// ==================== 工具函数 ====================
function loadTodos() {
if (!fs.existsSync(TODO_FILE)) {
return [];
}
const data = fs.readFileSync(TODO_FILE, 'utf-8');
return JSON.parse(data);
}

function saveTodos(todos) {
fs.writeFileSync(TODO_FILE, JSON.stringify(todos, null, 2));
}

function formatTodo(todo, index) {
const status = todo.done ? '✅' : '⬜';
const title = todo.done ? `̶${todo.title}̶` : todo.title;  // 简单删除线效果
let line = `${index + 1}. ${status} ${title}`;

if (SHOW_TIMESTAMP && todo.createdAt) {
line += ` [创建: ${new Date(todo.createdAt).toLocaleDateString()}]`;
}
if (SHOW_TIMESTAMP && todo.doneAt) {
line += ` [完成: ${new Date(todo.doneAt).toLocaleDateString()}]`;
}

return line;
}

// ==================== 操作函数 ====================
function list() {
const todos = loadTodos();
console.log('\n📋 我的待办清单');
console.log('─────────────────');
if (todos.length === 0) {
console.log('(空的,快去添加一个吧!)');
} else {
todos.forEach((todo, i) => console.log(formatTodo(todo, i)));
}
console.log('─────────────────\n');
}

function add(title) {
const todos = loadTodos();
todos.push({
title,
done: false,
createdAt: new Date().toISOString()
});
saveTodos(todos);
console.log(`✅ 已添加:「${title}」`);
}

function done(index) {
const todos = loadTodos();
if (index < 1 || index > todos.length) {
console.log('❌ 无效的序号!');
return;
}
todos[index - 1].done = true;
todos[index - 1].doneAt = new Date().toISOString();
saveTodos(todos);
console.log(`✅ 已完成:「${todos[index - 1].title}」`);
}

function deleteTodo(index) {
const todos = loadTodos();
if (index < 1 || index > todos.length) {
console.log('❌ 无效的序号!');
return;
}
const removed = todos.splice(index - 1, 1)[0];
saveTodos(todos);
console.log(`🗑️  已删除:「${removed.title}」`);
}

// ==================== 主程序 ====================
const action = process.argv[2] || 'list';
const arg = process.argv[3];

switch (action) {
case 'list':
list();
break;
case 'add':
if (!arg) {
  console.log('❌ 请输入待办内容:node project3_todo_list.js add "买牛奶"');
} else {
  add(arg);
}
break;
case 'done':
if (!arg) {
  console.log('❌ 请输入序号:node project3_todo_list.js done 1');
} else {
  done(parseInt(arg));
}
break;
case 'delete':
if (!arg) {
  console.log('❌ 请输入序号:node project3_todo_list.js delete 1');
} else {
  deleteTodo(parseInt(arg));
}
break;
default:
console.log('❓ 未知命令,可用:list | add | done | delete');
}

// 显示时间戳开关状态
console.log(`⏱️  时间戳显示:${SHOW_TIMESTAMP ? '开启' : '关闭'}(用 SHOW_TIMESTAMP=true 开启)`);

使用方法:

# 查看列表
node project3_todo_list.js list

# 添加待办
node project3_todo_list.js add "写完第二章教程"

# 标记完成
node project3_todo_list.js done 1

# 删除
node project3_todo_list.js delete 1

# 开启时间戳显示
SHOW_TIMESTAMP=true node project3_todo_list.js list

预期输出:

⏱️  时间戳显示:关闭(用 SHOW_TIMESTAMP=true 开启)

📋 我的待办清单
─────────────────
1. ⬜ 写完第二章教程
2. ✅ ̶买牛奶̶ [创建: 2024/1/15] [完成: 2024/1/15]
─────────────────

💡 这个项目综合运用了:os.homedir() 存数据、process.env 控制功能开关、process.argv 读取命令行参数。用环境变量控制功能是生产环境的常见做法


💪 进阶 20 分钟:常见坑 + 性能小贴士

坑1:路径分隔符不兼容

// ❌ 错误:写死了反斜杠(Windows风格)
const wrongPath = 'C:\\Users\\admin\\file.txt';

// ❌ 错误:用斜杠拼接(不同系统行为不同)
const badPath = '/home/' + 'user' + '/file.txt';

// ✅ 正确:用 path.join() 或 path.resolve()
const os = require('os');
const path = require('path');
const goodPath = path.join(os.homedir(), 'user', 'file.txt');

💡 path.join() 会自动选择正确的分隔符,永远不要手写路径


坑2:环境变量不存在时没处理

// ❌ 错误:直接用,可能导致程序崩溃
const apiKey = process.env.API_KEY;  // 如果没设置,apiKey 是 undefined
const result = apiKey.split(',');     // undefined.split() 会报错!

// ✅ 正确:提供默认值
const apiKey = process.env.API_KEY || 'default-key';

// ✅ 更好:明确检查
const apiKey = process.env.API_KEY;
if (!apiKey) {
console.error('❌ 请设置 API_KEY 环境变量');
process.exit(1);
}

💡 生产环境代码要防御性编程,假设所有外部输入都可能是 undefined。


坑3:process.env 性能误解

// ❌ 错误:在循环里频繁读取环境变量
for (let i = 0; i < 1000; i++) {
const value = process.env.SOME_VAR;  // 每次循环都查字典,慢!
}

// ✅ 正确:循环外读一次,循环内用变量
const value = process.env.SOME_VAR;
for (let i = 0; i < 1000; i++) {
// 用 value 而不是每次查 process.env
}

💡 process.env 访问虽然不慢,但在高频循环里还是能省则省。


坑4:cwd() 理解成文件所在目录

// ❌ 错误:以为这会输出当前JS文件所在的目录
console.log(__dirname);  // 这才是文件所在目录!

// ✅ 正确:这会输出「运行命令时」所在的目录
console.log(process.cwd());  // 你在哪个文件夹运行的 node 命令

💡 敲黑板!process.cwd() 是「你跑命令时站在哪」,__dirname 是「这个文件本身在哪」。用错会出大问题!


坑5:跨平台检测 platform() 值

// ❌ 错误:直接写死判断
if (os.platform() === 'mac') {  // 错了!实际值是 'darwin'
// Mac 专属代码
}

// ✅ 正确:使用正确的值
if (os.platform() === 'darwin') {
// Mac 专属代码
}

if (os.platform() === 'win32') {  // Windows 不是 'windows',是 'win32'!
// Windows 专属代码
}

💡 Windows 用户看到 'win32' 别慌,这是 Windows 的内部代号。Mac 是 'darwin'(它的内核名字)。


调试技巧:console.log 调试 + 快速定位

// 技巧1:打印完整对象(格式化输出)
console.log(JSON.stringify(process.env, null, 2));

// 技巧2:快速查看有哪些环境变量(只看KEY)
console.log(Object.keys(process.env));

// 技巧3:用 DEBUG 环境变量做条件输出
if (process.env.DEBUG === 'true') {
console.log('🔍 调试信息:', someVariable);
}

✏️ 练习题 + 作业题

练习题(10 分钟)

练习1(2分钟):读取系统信息
- 输入:运行 os.cpus() 并计算平均 CPU 使用率
- 预期输出:打印 CPU 核心数和平均频率
- 提示:os.cpus() 返回的每个 CPU 对象里有 speed 属性

练习2(2分钟):环境变量默认值
- 输入:读取 process.env.UNKNOWN_VAR,设置默认值为 "默认值"
- 预期输出:默认值
- 提示:用到 || 运算符

练习3(3分钟):跨平台路径拼接
- 输入:用 path.join() 拼接 os.homedir()'documents''notes.txt'
- 预期输出:完整路径字符串
- 提示:需要 require('path')

练习4(3分钟):改造项目1,加时间戳
- 输入:在项目1的输出上加一行 console.log('Node版本:', process.version)
- 预期输出:能看到 Node 版本号
- 提示:直接加一行 console.log()

练习5(5分钟):分析报错
- 输入:以下代码会输出什么?

const path = require('path');
const wrong = path.join('C:\\Users', 'admin');
console.log(wrong);
  • 预期输出:解释为什么输出不是你预期的
  • 提示:Windows 路径在字符串里要转义,但 path.join() 会帮你处理

作业题(30 分钟 - 2 小时)

作业:做一个「系统监控小助手」

📋 需求描述
做一个命令行工具,每隔几秒报告一次系统状态,让你了解服务器或电脑的运行状况。

功能点
1. 显示 CPU 使用率(可以取巧:用 os.cpus()times 属性)
2. 显示内存使用情况(已用 / 总共)
3. 显示进程运行时间
4. 每 5 秒刷新一次(用 setInterval
5. 支持按 Ctrl + C 优雅退出

加分项
1. 用环境变量控制刷新间隔(如 INTERVAL=3 node monitor.js
2. 颜色输出(用 console.log 的特殊格式,如 \x1b[32m 绿色)

验收标准
- 能跑起来
- 能显示 CPU 和内存信息
- 按 Ctrl + C 显示「再见」再退出
- 代码有注释

提交方式:评论区贴代码或 GitHub 链接


📚 总结 + 资源

一句话总结

这一章我们学会了用 os 模块透视电脑(CPU、内存、路径),用 process 对象透视程序(工作目录、环境变量、进程信息),做到了跨平台兼容 + 敏感信息安全管理。

延伸学习资源

  1. 📖 Node.js 官方文档 - os 模块 — 官方权威,例子最全
  2. 📖 Node.js 官方文档 - process 对象 — 全局对象,必须收藏
  3. 📺 YouTube: Node.js 进程管理教程 — 视频教程更适合视觉学习者

互动钩子

👨‍💻 你在实际项目里用过 os 或 process 模块吗?
比如读取环境变量配置 API、做跨平台兼容、还是监控系统资源?
评论区聊聊你的场景,老粉优先回复!


📢 下章预告
学完了「侦察」电脑和进程的能力,下一章我们要学 Node.js 的另一个核心能力——events 事件触发器

想象一下:当你敲击键盘时,Node.js 是怎么「感知」到的?当你收到网络请求时,谁在通知你的代码去处理?

下一章,我们会揭开 Node.js「事件驱动」的神秘面纱。


🎯 本章结束。恭喜你又解锁了 Node.js 的两个硬核模块!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。