01-Node.js 环境搭建与 ES6 基础
Node.js 是目前最流行的 JavaScript 服务端运行时环境。本章将从零基础开始,带你完成 Node.js 开发环境的搭建,系统学习 ES6+ 核心语法,并掌握 Node.js 的基础语法特性,为后续章节的学习打下坚实基础。
一、Node.js 环境搭建
在开始学习 Node.js 之前,我们需要先搭建好开发环境。本章节将详细介绍 Node.js 的下载安装、nvm 版本管理工具的使用、VS Code 编辑器的配置,以及如何编写并运行第一个 Node.js 程序。
1.1 Node.js 下载与安装
Node.js 版本说明
Node.js 官方提供了两种主要版本:
| 版本类型 |
特点 |
适用场景 |
| LTS(长期支持版) |
稳定可靠,维护周期长 |
生产环境、项目开发(推荐) |
| Current(当前版本) |
包含最新特性,更新频繁 |
体验新特性、学习研究 |
选择建议:新手或项目开发优先选择 LTS 版本,截至 2024 年 9 月,推荐使用 Node.js 20.x LTS 版本。
Windows 系统安装步骤
访问官网:打开 Node.js 官方网站 https://nodejs.org/zh-cn,点击左侧的 LTS 版本下载链接。
运行安装程序:下载完成后,双击 .msi 安装包,按照向导提示逐步安装:
- 同意许可协议
- 选择安装路径(默认即可)
- 勾选 “Add to PATH”(默认已勾选,非常重要)
- 点击”安装”按钮完成安装
验证安装:打开命令提示符(按 Win + R,输入 cmd 回车),执行以下命令:
如果显示版本号(如 v20.11.0),说明 Node.js 安装成功。
同时验证 npm(Node.js 包管理器)是否也已安装:
npm 会随 Node.js 一起安装,用于管理项目依赖包。
macOS 系统安装步骤
macOS 用户推荐使用 Homebrew 安装 Node.js:
1 2 3 4 5 6 7 8 9
|
brew install node
node -v npm -v
|
Linux 系统安装步骤
以 Ubuntu/Debian 为例:
1 2 3 4 5 6 7
| curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs
node -v npm -v
|
注意:不建议直接使用 apt install nodejs 安装,因为默认软件源中的 Node.js 版本通常较旧。
1.2 nvm 版本管理工具
在实际开发中,我们可能需要在不同项目中使用不同版本的 Node.js。nvm(Node Version Manager)就是专门用于管理 Node.js 多版本的工具。
为什么需要 nvm
不同项目可能依赖不同版本的 Node.js。例如,一个旧项目可能需要 Node.js 14.x,而新项目使用 Node.js 20.x。频繁卸载重装 Node.js 不仅麻烦,还容易出问题。nvm 可以让我们在同一台机器上安装多个 Node.js 版本,并随时切换。
安装 nvm
Windows 系统:
Windows 用户推荐使用 nvm-windows,从 GitHub 下载安装包:
macOS / Linux 系统:
1 2 3 4 5 6 7
| curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm --version
|
nvm 常用命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| nvm ls-remote
nvm install 20.11.0
nvm install --lts
nvm ls
nvm use 20.11.0
nvm alias default 20.11.0
nvm uninstall 20.11.0
|
使用 nvm 切换版本示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| nvm ls
nvm use 18.19.0
node -v
nvm use 20
|
1.3 VS Code 开发环境配置
VS Code(Visual Studio Code)是目前最流行的免费代码编辑器,拥有丰富的扩展生态,非常适合 Node.js 开发。
安装 VS Code
- 访问官网 https://code.visualstudio.com/ 下载安装
- 按照安装向导完成安装
必装扩展插件
打开 VS Code 后,点击左侧扩展图标(或按 Ctrl+Shift+X),搜索并安装以下扩展:
| 扩展名 |
功能说明 |
推荐理由 |
| ESLint |
JavaScript/TypeScript 代码规范检查 |
保持代码风格统一,快速发现潜在问题 |
| Prettier - Code formatter |
代码格式化工具 |
自动格式化代码,团队协作必备 |
| REST Client |
在编辑器内直接发送 HTTP 请求 |
调试 API 接口非常方便 |
| GitLens |
增强 Git 功能 |
查看代码历史、行级 blame 信息 |
| Path Intellisense |
路径自动补全 |
减少路径输入错误 |
| Node.js Exec |
一键运行/调试 Node.js 代码 |
快速运行当前文件 |
配置代码编辑器
在 VS Code 中按 Ctrl + , 打开设置,搜索并配置以下选项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| { "editor.tabSize": 2, "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "files.encoding": "utf8", "terminal.integrated.defaultProfile.windows": "PowerShell" }
|
在 VS Code 中运行 Node.js
- 打开项目文件夹:点击”文件” → “打开文件夹”,选择你的项目目录
- 创建 JS 文件:在资源管理器中右键新建文件,命名为
app.js
- 编写代码:在编辑器中输入 JavaScript 代码
- 运行程序:
- 方法一:使用快捷键
F5 启动调试
- 方法二:在 VS Code 终端(
Ctrl + ``)中执行 node app.js`
- 方法三:安装 “Code Runner” 扩展后,右上角会出现运行按钮
1.4 第一个 Node.js 程序
创建项目目录
1 2 3 4 5
| mkdir my-first-node-app
cd my-first-node-app
|
初始化项目
这个命令会创建一个 package.json 文件,它是 Node.js 项目的配置文件,记录了项目名称、版本、依赖等信息。
编写第一个程序
在项目目录下创建 app.js 文件,编写以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
const greeting = 'Hello, Node.js!'; console.log(greeting);
let sum = 0; for (let i = 1; i <= 10; i++) { sum += i; } console.log('1 到 10 的和是:', sum);
const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); console.log('原数组:', numbers); console.log('翻倍后:', doubled);
const os = require('os'); console.log('当前系统:', os.platform()); console.log('CPU 架构:', os.arch()); console.log('系统内存:', (os.totalmem() / 1024 / 1024 / 1024).toFixed(2), 'GB');
|
运行程序
预期输出结果:
1 2 3 4 5 6 7
| Hello, Node.js! 1 到 10 的和是: 55 原数组: [ 1, 2, 3, 4, 5 ] 翻倍后: [ 2, 4, 6, 8, 10 ] 当前系统: win32 CPU 架构: x64 系统内存: 16.00 GB
|
代码解析
| 代码行 |
说明 |
const greeting = 'Hello, Node.js!' |
使用 const 声明常量,这是 ES6 的新特性 |
for (let i = 1; i <= 10; i++) |
使用 let 声明循环变量,具有块级作用域 |
numbers.map(n => n * 2) |
使用箭头函数作为回调,简洁直观 |
const os = require('os') |
使用 require() 引入 Node.js 内置模块 |
os.platform() |
获取操作系统平台信息 |
二、ES6+ 核心语法
ES6(即 ECMAScript 2015)是 JavaScript 语言的一次重大更新,引入了许多新特性。此后,ECMAScript 每年都在发布新版本(ES2016、ES2017…),这些新特性被统称为 ES6+。掌握这些语法特性是学习 Node.js 的前提。
2.1 let 与 const
从 var 到 let/const
在 ES6 之前,JavaScript 使用 var 关键字声明变量。var 声明的变量存在变量提升和无块级作用域等问题。ES6 引入了 let 和 const 来解决这些问题。
let 关键字
let 用于声明变量,它的特点:
- 块级作用域:只在声明它的代码块内有效
- 无变量提升:必须先声明后使用
- 暂时性死区:在声明之前不能访问该变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| if (true) { let a = 10; console.log(a); }
let b = 20;
var num = 100; if (true) { let num = 200; }
|
const 关键字
const 用于声明常量(值不可变的变量),它的特点:
- 具有
let 的所有特性(块级作用域、无变量提升、暂时性死区)
- 声明时必须赋值
- 赋值后不能重新赋值(对于基本类型,值不可变;对于引用类型,引用不可变)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
const PI = 3.14159;
const user = { name: '张三', age: 20 }; user.age = 21;
const colors = ['red', 'green']; colors.push('blue');
|
var、let、const 的区别
| 特性 |
var |
let |
const |
| 作用域 |
函数作用域 |
块级作用域 |
块级作用域 |
| 变量提升 |
✅ 有 |
❌ 无 |
❌ 无 |
| 暂时性死区 |
❌ 无 |
✅ 有 |
✅ 有 |
| 重复声明 |
✅ 可以 |
❌ 不可以 |
❌ 不可以 |
| 值是否可变 |
✅ 可以 |
✅ 可以 |
❌ 不可以 |
最佳实践:在实际开发中,优先使用 const,只有当确定需要重新赋值时才使用 let,尽量避免使用 var。
2.2 箭头函数
箭头函数是 ES6 中新增的函数定义方式,用于简化函数的语法。
基本语法
1 2 3 4 5 6 7 8 9 10 11 12 13
| function add(a, b) { return a + b; }
const addArrow = (a, b) => { return a + b; };
console.log(add(1, 2)); console.log(addArrow(1, 2));
|
简写形式
当函数体只有一条表达式时,可以省略 {} 和 return:
1 2 3 4 5 6 7 8 9 10 11
| const multiply = (a, b) => a * b; console.log(multiply(3, 4));
const square = x => x * x; console.log(square(5));
const getTime = () => Date.now(); console.log(getTime());
|
箭头函数的 this 指向
箭头函数没有自己的 this,它会捕获定义时所在作用域的 this。这是它与传统函数最大的区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const obj = { name: '张三', sayHello: function() { console.log('传统函数 this.name:', this.name); return function() { console.log('内部函数 this.name:', this.name); }; }, sayArrow: function() { console.log('箭头函数 this.name:', this.name); return () => { console.log('箭头函数 this.name:', this.name); }; } };
const traditional = obj.sayHello(); traditional();
const arrow = obj.sayArrow(); arrow();
|
注意:箭头函数不能作为构造函数,也没有 arguments 对象。在需要使用 this 或 arguments 的场景下,应当使用普通函数。
2.3 解构赋值
解构赋值是一种从数组或对象中提取值并赋给变量的简洁语法。
数组解构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let [a, b, c] = [1, 2, 3]; console.log(a, b, c);
let [x, y] = [1, 2, 3, 4]; console.log(x, y);
let [first, ...rest] = [1, 2, 3, 4, 5]; console.log(first); console.log(rest);
let [name = '默认值', age = 18] = ['张三']; console.log(name, age);
let m = 10, n = 20; [m, n] = [n, m]; console.log(m, n);
|
对象解构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const person = { name: '李四', age: 25, gender: '男' };
let { name, age } = person; console.log(name, age);
let { name: userName, age: userAge } = person; console.log(userName, userAge);
let { name: n = '匿名', job = '未知' } = person; console.log(n, job);
const user = { info: { name: '王五', age: 30 }, scores: [95, 87, 92] }; let { info: { name: uname }, scores: [math, ...otherScores] } = user; console.log(uname); console.log(math); console.log(otherScores);
function greet({ name, age }) { return `你好,${name}!你今年 ${age} 岁。`; } console.log(greet(person));
|
2.4 模板字符串
模板字符串使用反引号(`)定义,支持多行文本和变量嵌入。
基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let str1 = 'Hello\nWorld';
let str2 = `Hello World`; console.log(str2);
let name = '赵六'; let age = 28; let message = `姓名:${name},年龄:${age}`; console.log(message);
|
模板字符串的高级应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function getAge(birthYear) { return new Date().getFullYear() - birthYear; } let info = `我今年 ${getAge(1996)} 岁了。`; console.log(info);
let price = 100; let discount = 0.8; let total = `原价 ${price} 元,打折后 ${price * discount} 元。`; console.log(total);
const users = [ { name: '张三', email: 'zhangsan@example.com' }, { name: '李四', email: 'lisi@example.com' } ];
const html = ` <ul> ${users.map(u => `<li>${u.name} - ${u.email}</li>`).join('\n ')} </ul> `; console.log(html);
|
2.5 扩展运算符
扩展运算符用三个点(...)表示,可以将数组或对象”展开”。
数组扩展运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const numbers = [1, 2, 3]; console.log(...numbers);
function sum(a, b, c) { return a + b + c; } console.log(sum(...numbers));
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const merged = [...arr1, ...arr2]; console.log(merged);
const original = [1, 2, 3]; const copy = [...original]; console.log(copy); console.log(copy === original);
function toArray() { return [...arguments]; } console.log(toArray(1, 2, 3));
|
对象扩展运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const obj1 = { a: 1, b: 2 }; const obj2 = { c: 3, d: 4 }; const mergedObj = { ...obj1, ...obj2 }; console.log(mergedObj);
const originalObj = { name: '张三', info: { age: 20 } }; const copyObj = { ...originalObj }; console.log(copyObj);
const defaults = { color: 'red', size: 'medium', weight: 5 }; const userConfig = { color: 'blue', size: 'large' }; const finalConfig = { ...defaults, ...userConfig }; console.log(finalConfig);
const { weight, ...rest } = defaults; console.log(rest);
|
2.6 Promise
Promise 是异步编程的一种解决方案,用于处理异步操作的回调嵌套问题。
Promise 基础概念
Promise 有三种状态:
| 状态 |
说明 |
| pending |
进行中,初始状态 |
| fulfilled |
已成功 |
| rejected |
已失败 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const promise = new Promise((resolve, reject) => { setTimeout(() => { const success = true; if (success) { resolve('操作成功!'); } else { reject(new Error('操作失败!')); } }, 1000); });
promise .then(result => { console.log('成功:', result); }) .catch(error => { console.log('失败:', error.message); });
|
Promise 链式调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function step1() { return new Promise((resolve) => { setTimeout(() => resolve('步骤1完成'), 500); }); }
function step2(result) { return new Promise((resolve) => { setTimeout(() => resolve(result + ' → 步骤2完成'), 500); }); }
function step3(result) { return new Promise((resolve) => { setTimeout(() => resolve(result + ' → 步骤3完成'), 500); }); }
step1() .then(step2) .then(step3) .then(finalResult => { console.log('最终结果:', finalResult); }) .catch(error => { console.log('出错了:', error.message); });
|
Promise 静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const p1 = Promise.resolve(1); const p2 = Promise.resolve(2); const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]) .then(results => { console.log('所有结果:', results); });
const slow = new Promise(resolve => setTimeout(() => resolve('慢'), 2000)); const fast = new Promise(resolve => setTimeout(() => resolve('快'), 500));
Promise.race([slow, fast]) .then(result => { console.log('最先完成:', result); });
const resolved = Promise.resolve('立即成功'); const rejected = Promise.reject(new Error('立即失败'));
|
2.7 async/await
async/await 是 ES2017 引入的语法,它让异步代码看起来像同步代码,极大地提高了代码的可读性。
基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13
| async function fetchData() { const result = await new Promise((resolve) => { setTimeout(() => resolve('数据获取成功'), 1000); }); return result; }
fetchData().then(data => { console.log(data); });
|
错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| async function fetchUser(userId) { try { const response = await new Promise((resolve, reject) => { setTimeout(() => { if (userId > 0) { resolve({ id: userId, name: '张三' }); } else { reject(new Error('无效的用户 ID')); } }, 500); }); return response; } catch (error) { console.log('捕获错误:', error.message); return null; } }
fetchUser(1).then(user => console.log(user)); fetchUser(-1).then(user => console.log(user));
|
并行执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| async function loadAllData() { try { const [users, products, orders] = await Promise.all([ new Promise(resolve => setTimeout(() => resolve('用户列表'), 500)), new Promise(resolve => setTimeout(() => resolve('商品列表'), 800)), new Promise(resolve => setTimeout(() => resolve('订单列表'), 600)) ]);
console.log('用户:', users); console.log('商品:', products); console.log('订单:', orders); } catch (error) { console.log('加载失败:', error.message); } }
loadAllData();
|
实战对比:回调 vs Promise vs async/await
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function getDataCallback(callback) { setTimeout(() => { callback(null, '数据'); }, 500); }
getDataCallback((err, data) => { if (err) throw err; console.log('回调:', data); });
function getDataPromise() { return new Promise((resolve) => { setTimeout(() => resolve('数据'), 500); }); }
getDataPromise().then(data => console.log('Promise:', data));
async function getDataAsync() { const data = await getDataPromise(); console.log('async/await:', data); }
getDataAsync();
|
最佳实践:在现代 Node.js 开发中,优先使用 async/await 处理异步操作,代码更加清晰易读。同时配合 try/catch 进行错误处理。
三、Node.js 基础语法
Node.js 在 ECMAScript 的基础上,提供了一些特有的全局对象和 API。本章节将介绍 Node.js 中最重要的基础语法特性。
3.1 全局对象 global 与 globalThis
global 对象
在浏览器中,全局变量挂载在 window 对象上;在 Node.js 中,全局变量挂载在 global 对象上。
1 2 3 4 5 6 7 8 9 10
|
global.console.log('hello');
global.setTimeout(() => { console.log('延迟执行'); }, 1000);
|
注意:在 Node.js 中,使用 var/let/const 声明的变量不会挂载到 global 上,这与浏览器的行为不同:
1 2 3 4 5 6 7 8
| var a = 1; let b = 2; const c = 3;
console.log(global.a); console.log(global.b); console.log(global.c);
|
这是因为 Node.js 默认每个文件都是一个模块,模块内声明的变量是私有的,不会污染全局作用域。
globalThis 对象
ES2020 引入了 globalThis,它在任何环境中都指向全局对象:
1 2 3 4 5 6 7 8 9
| console.log(globalThis === global);
globalThis.myGlobalVariable = '这是全局变量'; console.log(globalThis.myGlobalVariable);
|
global 对象的常用属性和方法
| 属性/方法 |
说明 |
global.console |
控制台对象,用于输出信息 |
global.process |
进程对象,获取进程信息 |
global.setTimeout() |
设置定时器 |
global.setInterval() |
设置间隔定时器 |
global.clearTimeout() |
清除定时器 |
global.Buffer |
二进制数据处理类 |
3.2 __dirname 与 __filename
Node.js 提供了两个特殊的变量:__dirname 和 __filename,它们在每个模块内都有定义。
__dirname
__dirname 表示当前模块文件所在目录的绝对路径:
1 2 3 4 5
|
console.log(__dirname);
|
__filename
__filename 表示当前模块文件的绝对路径(包含文件名):
1 2 3 4 5 6 7 8
|
console.log(__filename);
console.log(__dirname);
|
实际应用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const path = require('path'); const fs = require('fs');
const configPath = path.join(__dirname, 'config.json'); console.log('配置文件路径:', configPath);
const projectRoot = path.resolve(__dirname, '../..'); const dataFilePath = path.join(projectRoot, 'data', 'users.json'); console.log('数据文件路径:', dataFilePath);
|
注意:__dirname 和 __filename 仅在 CommonJS 模块中可用。在 ES Modules(import/export)中,需要通过 import.meta.url 来获取类似信息。
3.3 process 对象
process 对象是 Node.js 中一个非常重要的全局对象,提供了与当前进程相关的信息和操作方法。
process.argv
process.argv 是一个数组,包含命令行参数:
1 2 3 4 5 6
| console.log('process.argv:', process.argv); console.log('参数数量:', process.argv.length); console.log('第一个参数(Node路径):', process.argv[0]); console.log('第二个参数(脚本路径):', process.argv[1]); console.log('第三个及以后(用户参数):', process.argv.slice(2));
|
运行结果:
1
| node args.js hello world 42
|
1 2 3 4 5 6 7 8 9 10 11
| process.argv: [ 'C:\\Program Files\\nodejs\\node.exe', 'C:\\Projects\\my-app\\args.js', 'hello', 'world', '42' ] 参数数量: 5 第一个参数(Node路径): C:\Program Files\nodejs\node.exe 第二个参数(脚本路径): C:\Projects\my-app\args.js 第三个及以后(用户参数): [ 'hello', 'world', '42' ]
|
process.env
process.env 返回一个包含所有环境变量的对象:
1 2 3 4 5 6 7 8 9 10 11 12 13
| console.log('HOME 目录:', process.env.HOME); console.log('PATH:', process.env.PATH);
process.env.NODE_ENV = 'development'; console.log('当前环境:', process.env.NODE_ENV);
console.log('MY_VAR:', process.env.MY_VAR);
|
process 常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
process.on('exit', (code) => { console.log('进程退出,退出码:', code); });
process.on('uncaughtException', (err) => { console.log('未捕获的异常:', err.message); process.exit(1); });
console.log('当前目录:', process.cwd());
console.log('进程 PID:', process.pid);
console.log('Node.js 版本:', process.version);
console.log('CPU 架构:', process.arch);
console.log('操作系统:', process.platform);
|
process.stdin/stdout/stderr
process 提供了标准输入、输出和错误流:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| process.stdin.setEncoding('utf8');
process.stdin.on('data', (data) => { const input = data.toString().trim(); console.log('你输入了:', input);
process.stdout.write('回显: ' + input + '\n');
if (input === 'quit') { process.exit(0); } });
console.log('请输入内容(输入 quit 退出):'); process.stdout.write('> ');
|
运行效果:
1 2 3 4 5 6
| 请输入内容(输入 quit 退出): > Hello Node.js 你输入了: Hello Node.js 回显: Hello Node.js > quit 进程退出,退出码: 0
|
四、实战练习:温度转换器 CLI 工具
本章节将综合运用前面所学的知识,创建一个实用的命令行工具——温度转换器。该工具支持摄氏度、华氏度、开尔文之间的相互转换。
4.1 项目目标
- 接收命令行输入的温度值和单位
- 实现摄氏度(°C)、华氏度(°F)、开尔文(K)之间的相互转换
- 支持交互式输入模式和命令行参数模式
- 输出格式化的转换结果
4.2 创建项目
1 2 3 4
| mkdir temperature-converter cd temperature-converter npm init -y
|
4.3 编写代码
创建 index.js 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
| #!/usr/bin/env node
const readline = require('readline');
const UNITS = { CELSIUS: 'C', FAHRENHEIT: 'F', KELVIN: 'K' };
const UNIT_NAMES = { C: '摄氏度 (°C)', F: '华氏度 (°F)', K: '开尔文 (K)' };
function convertTemperature(value, fromUnit, toUnit) { value = Number(value); if (isNaN(value)) { throw new Error('温度值必须是数字'); }
fromUnit = fromUnit.toUpperCase(); toUnit = toUnit.toUpperCase();
if (![UNITS.CELSIUS, UNITS.FAHRENHEIT, UNITS.KELVIN].includes(fromUnit)) { throw new Error(`不支持的源温度单位: ${fromUnit},支持的单位: C, F, K`); } if (![UNITS.CELSIUS, UNITS.FAHRENHEIT, UNITS.KELVIN].includes(toUnit)) { throw new Error(`不支持的目标温度单位: ${toUnit},支持的单位: C, F, K`); }
if (fromUnit === toUnit) { return { value, unit: toUnit }; }
let celsius; switch (fromUnit) { case UNITS.CELSIUS: celsius = value; break; case UNITS.FAHRENHEIT: celsius = (value - 32) * 5 / 9; break; case UNITS.KELVIN: celsius = value - 273.15; break; }
let result; switch (toUnit) { case UNITS.CELSIUS: result = celsius; break; case UNITS.FAHRENHEIT: result = celsius * 9 / 5 + 32; break; case UNITS.KELVIN: result = celsius + 273.15; break; }
return { value: Math.round(result * 100) / 100, unit: toUnit }; }
function printResult(fromValue, fromUnit, result) { console.log('\n========================================'); console.log(' 温度转换结果'); console.log('========================================'); console.log(` 源温度: ${fromValue} ${UNIT_NAMES[fromUnit.toUpperCase()]}`); console.log(` 目标温度: ${result.value} ${UNIT_NAMES[result.unit]}`); console.log('========================================\n'); }
function parseArgs(args) { const argsStr = args.join(' ');
if (argsStr.includes('--help') || argsStr.includes('-h')) { console.log(` 温度转换器 - 命令行使用方法:
node index.js <温度值> <源单位> <目标单位>
参数说明: 温度值 - 要转换的数值(如 100) 源单位 - 原始温度单位(C=摄氏度, F=华氏度, K=开尔文) 目标单位 - 目标温度单位(C=摄氏度, F=华氏度, K=开尔文)
示例: node index.js 100 C F # 100 摄氏度转华氏度 node index.js 32 F C # 32 华氏度转摄氏度 node index.js 0 K C # 0 开尔文转摄氏度
交互式模式: node index.js # 不带参数启动交互式模式 `); process.exit(0); }
if (args.length === 3) { const [value, from, to] = args; return { value, fromUnit: from, toUnit: to }; }
return null; }
function runInteractive() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
console.log('\n🌡️ 温度转换器(交互式模式)'); console.log('输入 help 查看帮助,输入 quit 退出\n');
function askValue() { rl.question('请输入温度值: ', (value) => { if (value.toLowerCase() === 'quit') { rl.close(); return; } if (value.toLowerCase() === 'help') { console.log('支持的单位: C=摄氏度, F=华氏度, K=开尔文'); askValue(); return; }
askUnit('请输入源单位 (C/F/K): ', (fromUnit) => { askUnit('请输入目标单位 (C/F/K): ', (toUnit) => { try { const result = convertTemperature(value, fromUnit, toUnit); printResult(value, fromUnit, result); } catch (err) { console.log('❌ 错误:', err.message); } askValue(); }); }); }); }
function askUnit(prompt, callback) { rl.question(prompt, (unit) => { if (unit.toLowerCase() === 'quit') { rl.close(); return; } callback(unit); }); }
askValue(); }
function main() { const args = process.argv.slice(2); const parsed = parseArgs(args);
if (parsed) { try { const result = convertTemperature(parsed.value, parsed.fromUnit, parsed.toUnit); printResult(parsed.value, parsed.fromUnit, result); } catch (err) { console.log('❌ 错误:', err.message); console.log('使用 --help 查看帮助信息'); process.exit(1); } } else { runInteractive(); } }
main();
|
4.4 添加 npm 脚本(可选)
编辑 package.json,在 scripts 部分添加快捷命令:
1 2 3 4 5 6
| { "scripts": { "start": "node index.js", "convert": "node index.js" } }
|
4.5 运行测试
命令行参数模式
1 2 3 4 5 6 7 8 9 10 11
| node index.js 100 C F
node index.js 32 F C
node index.js 0 C K
node index.js --help
|
交互式模式
预期交互效果:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 🌡️ 温度转换器(交互式模式) 输入 help 查看帮助,输入 quit 退出
请输入温度值: 100 请输入源单位 (C/F/K): C 请输入目标单位 (C/F/K): F
======================================== 温度转换结果 ======================================== 源温度: 100 摄氏度 (°C) 目标温度: 212 华氏度 (°F) ========================================
|
4.6 代码知识点总结
| 知识点 |
代码位置 |
说明 |
require() |
第 3 行 |
引入 Node.js 内置的 readline 模块 |
const/let |
全局 |
使用 const 声明常量,let 声明变量 |
| 箭头函数 |
多处 |
简化函数定义,如 printResult、parseArgs |
| 解构赋值 |
第 139 行 |
const [value, from, to] = args |
| 模板字符串 |
第 112-125 行 |
帮助信息中使用模板字符串 |
| 扩展运算符 |
第 101 行 |
args.join(' ') 处理命令行参数 |
| Promise/async |
第 162-195 行 |
readline 基于事件回调,体现异步编程思想 |
process.argv |
第 99、205 行 |
获取命令行参数 |
process.exit() |
多处 |
控制进程退出 |
readline 模块 |
第 158-201 行 |
实现交互式命令行输入 |
__dirname/__filename |
未使用 |
可作为练习,尝试用它们构建配置文件路径 |
4.7 扩展练习
在完成基础功能后,可以尝试以下扩展:
- 添加历史记录功能:将每次转换记录保存到本地
history.json 文件中
- 支持更多单位:添加兰氏度(Rankine)等温度单位
- 批量转换:支持一次输入多个温度值进行批量转换
- 添加颜色输出:使用
chalk 包实现彩色终端输出
- 错误处理增强:对非法输入提供更友好的错误提示
- 添加单元测试:使用
Jest 或 Mocha 为核心转换函数编写测试用例
本章小结
本章系统学习了 Node.js 的基础知识,内容涵盖四个部分:
一、Node.js 环境搭建
- 学会了从官网下载安装 Node.js,验证安装是否成功
- 掌握了使用 nvm 管理 Node.js 多版本的方法
- 了解了 VS Code 的配置和常用扩展插件
- 完成了第一个 Node.js 程序的编写与运行
二、ES6+ 核心语法
- let/const:使用块级作用域的变量声明替代 var
- 箭头函数:简化函数定义,理解 this 指向规则
- 解构赋值:从数组和对象中高效提取数据
- 模板字符串:使用反引号和
${} 嵌入变量
- 扩展运算符:使用
... 展开数组和对象
- Promise/async-await:掌握异步编程的核心方法
三、Node.js 基础语法
- global/globalThis:理解 Node.js 的全局对象体系
- __dirname/__filename:获取当前模块的路径信息
- process 对象:掌握命令行参数、环境变量、进程控制等常用操作
四、实战练习
- 综合运用所学知识,完成了一个温度转换器 CLI 工具
- 支持命令行参数和交互式两种使用模式
- 涵盖了 readline 模块、文件操作、错误处理等实际开发场景
完成本章学习后,你已经具备了 Node.js 开发的基础能力。下一章将深入学习 Node.js 的模块化开发,包括 CommonJS、ES Modules、npm 包管理等核心内容。