正文
1. Node.js简介
Node.js是一个开源、跨平台的JavaScript运行环境,它允许开发者使用JavaScript来编写服务器端代码。Node.js采用Google Chrome的V8引擎来执行JavaScript代码,具有非阻塞I/O和事件驱动机制,使其成为构建高性能网络应用的理想选择。
1.1 Node.js的核心特点
- 非阻塞I/O:执行I/O操作时不会阻塞线程
- 事件驱动:基于事件循环处理并发操作
- 单线程:使用单线程处理多个并发连接
- 跨平台:可在Windows、macOS和Linux等多种操作系统上运行
- npm:拥有世界上最大的开源代码库生态系统
1.2 Node.js适用场景
Node.js特别适合以下场景:
- 实时应用(聊天、游戏服务器)
- REST API和微服务
- 单页面应用(SPA)的后端
- 流处理和数据密集型应用
- 命令行工具
2. 第一个Node.js程序
2.1 创建并运行Hello World
创建一个名为hello.js
的文件,内容如下:
// 第一个Node.js程序
console.log("Hello, Node.js!");
在命令行中运行:
node hello.js
输出结果:
Hello, Node.js!
2.2 创建简单的HTTP服务器
创建一个基本的Web服务器:
// 创建简单的HTTP服务器
const http = require('http');
// 创建HTTP服务器
const server = http.createServer((req, res) => {
// 设置响应头
res.writeHead(200, {'Content-Type': 'text/plain'});
// 发送响应数据
res.end('Hello World from Node.js Server!');
});
// 服务器监听3000端口
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
运行服务器:
node server.js
现在可以在浏览器中访问http://localhost:3000
查看结果。
3. Node.js核心概念
3.1 模块系统
Node.js使用CommonJS模块系统,允许将代码分割成可重用的部分。
3.1.1 创建和导出模块
// math.js - 创建一个数学工具模块
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
// 导出多个函数
module.exports = {
add,
subtract,
multiply,
divide
};
3.1.2 导入和使用模块
// app.js - 使用数学工具模块
const math = require('./math');
console.log(`2 + 3 = ${math.add(2, 3)}`);
console.log(`5 - 2 = ${math.subtract(5, 2)}`);
console.log(`4 * 6 = ${math.multiply(4, 6)}`);
console.log(`10 / 2 = ${math.divide(10, 2)}`);
// 也可以使用解构赋值
const { add, multiply } = require('./math');
console.log(`4 + 5 = ${add(4, 5)}`);
console.log(`3 * 7 = ${multiply(3, 7)}`);
3.1.3 ES模块语法(ES Modules)
Node.js也支持ES模块语法,需要将文件后缀改为.mjs
或在package.json
中设置"type": "module"
。
// mathES.mjs - ES模块语法
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// 导出默认值
export default {
name: 'Math Utils',
version: '1.0.0'
};
// appES.mjs - 导入ES模块
import { add, subtract } from './mathES.mjs';
import mathInfo from './mathES.mjs';
console.log(`ES模块: 2 + 3 = ${add(2, 3)}`);
console.log(`模块信息: ${mathInfo.name} v${mathInfo.version}`);
3.2 事件循环
Node.js的事件循环是其非阻塞I/O模型的核心,它允许Node.js执行非阻塞操作。
3.2.1 事件循环示例
console.log('1. 开始执行');
// 延迟执行 (宏任务)
setTimeout(() => {
console.log('4. setTimeout 回调执行');
}, 0);
// Promise (微任务)
Promise.resolve().then(() => {
console.log('3. Promise 回调执行');
});
console.log('2. 结束执行');
// 输出顺序:
// 1. 开始执行
// 2. 结束执行
// 3. Promise 回调执行
// 4. setTimeout 回调执行
3.2.2 异步文件操作示例
const fs = require('fs');
console.log('1. 开始读取文件');
// 异步读取文件
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取文件出错:', err);
return;
}
console.log('3. 文件内容:', data);
});
console.log('2. 读取文件的请求已发出,继续执行其他操作');
3.3 事件发射器 (EventEmitter)
EventEmitter是Node.js的核心模块,用于实现事件驱动架构。
const EventEmitter = require('events');
// 创建事件发射器实例
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
// 注册事件监听器
myEmitter.on('event', (a, b) => {
console.log('事件发生了!', a, b);
});
// 注册只执行一次的事件监听器
myEmitter.once('onceEvent', () => {
console.log('这个事件只会触发一次');
});
// 触发事件
myEmitter.emit('event', 'a', 'b');
myEmitter.emit('onceEvent');
myEmitter.emit('onceEvent'); // 这次不会触发监听器
3.3.1 自定义事件发射器
const EventEmitter = require('events');
// 创建一个用户类,继承EventEmitter
class User extends EventEmitter {
constructor(name) {
super();
this.name = name;
}
sayHello() {
console.log(`${this.name} says hello!`);
// 触发事件
this.emit('hello', this.name);
}
sayGoodbye() {
console.log(`${this.name} says goodbye!`);
// 触发事件
this.emit('goodbye', this.name);
}
}
// 创建用户实例
const user = new User('John');
// 添加事件监听器
user.on('hello', (name) => {
console.log(`Hello event was triggered by ${name}`);
});
user.on('goodbye', (name) => {
console.log(`Goodbye event was triggered by ${name}`);
});
// 调用方法,触发事件
user.sayHello();
user.sayGoodbye();
3.4 流 (Streams)
流是用于处理读写数据的抽象接口,尤其适合处理大文件。
3.4.1 流的类型
- 可读流 (Readable):用于读取数据
- 可写流 (Writable):用于写入数据
- 双工流 (Duplex):可读可写
- 转换流 (Transform):在读写过程中修改数据
3.4.2 文件流示例
const fs = require('fs');
// 创建可读流
const readStream = fs.createReadStream('source.txt', 'utf8');
// 创建可写流
const writeStream = fs.createWriteStream('destination.txt');
// 处理流事件
readStream.on('data', (chunk) => {
console.log(`接收到 ${chunk.length} 字节的数据`);
// 写入数据到可写流
writeStream.write(chunk);
});
readStream.on('end', () => {
writeStream.end(); // 结束写入流
console.log('读取完成');
});
readStream.on('error', (err) => {
console.error('读取错误:', err);
});
writeStream.on('finish', () => {
console.log('写入完成');
});
writeStream.on('error', (err) => {
console.error('写入错误:', err);
});
3.4.3 使用pipe()简化流操作
const fs = require('fs');
// 创建可读流和可写流
const readStream = fs.createReadStream('source.txt');
const writeStream = fs.createWriteStream('destination.txt');
// 使用pipe直接将可读流连接到可写流
readStream.pipe(writeStream);
// 处理事件
readStream.on('end', () => {
console.log('读取完成');
});
writeStream.on('finish', () => {
console.log('写入完成');
});
3.4.4 创建自定义流
const { Transform } = require('stream');
// 创建转换流,将文本转为大写
class UppercaseTransform extends Transform {
_transform(chunk, encoding, callback) {
// 转换数据
const upperChunk = chunk.toString().toUpperCase();
// 推送转换后的数据
this.push(upperChunk);
// 调用回调,表示处理完成
callback();
}
}
// 使用自定义转换流
const upperCaseStream = new UppercaseTransform();
// 从标准输入读取,经过转换后写入标准输出
process.stdin
.pipe(upperCaseStream)
.pipe(process.stdout);
4. 文件系统操作
Node.js提供了fs
模块用于与文件系统交互。
4.1 同步与异步文件操作
const fs = require('fs');
// 同步读取文件(阻塞)
try {
const data = fs.readFileSync('file.txt', 'utf8');
console.log('同步读取文件:', data);
} catch (err) {
console.error('同步读取错误:', err);
}
// 异步读取文件(非阻塞)
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('异步读取错误:', err);
return;
}
console.log('异步读取文件:', data);
});
// 使用Promise API (Node.js 10+)
fs.promises.readFile('file.txt', 'utf8')
.then(data => {
console.log('Promise读取文件:', data);
})
.catch(err => {
console.error('Promise读取错误:', err);
});
// 使用async/await (Node.js 10+)
async function readFileAsync() {
try {
const data = await fs.promises.readFile('file.txt', 'utf8');
console.log('Async/Await读取文件:', data);
} catch (err) {
console.error('Async/Await读取错误:', err);
}
}
readFileAsync();
4.2 写入文件
const fs = require('fs');
// 同步写入
try {
fs.writeFileSync('output1.txt', 'Hello, Node.js! (同步写入)', 'utf8');
console.log('同步写入完成');
} catch (err) {
console.error('同步写入错误:', err);
}
// 异步写入
fs.writeFile('output2.txt', 'Hello, Node.js! (异步写入)', 'utf8', (err) => {
if (err) {
console.error('异步写入错误:', err);
return;
}
console.log('异步写入完成');
});
// 追加内容到文件
fs.appendFile('output2.txt', '\n这是追加的内容', 'utf8', (err) => {
if (err) {
console.error('追加错误:', err);
return;
}
console.log('追加完成');
});
4.3 目录操作
const fs = require('fs');
const path = require('path');
// 创建目录
fs.mkdir('new-directory', (err) => {
if (err) {
console.error('创建目录错误:', err);
return;
}
console.log('目录创建成功');
});
// 递归创建多级目录
fs.mkdir('parent/child/grandchild', { recursive: true }, (err) => {
if (err) {
console.error('创建多级目录错误:', err);
return;
}
console.log('多级目录创建成功');
});
// 读取目录内容
fs.readdir('.', (err, files) => {
if (err) {
console.error('读取目录错误:', err);
return;
}
console.log('当前目录文件:');
files.forEach(file => {
console.log(`- ${file}`);
});
});
// 获取文件信息
fs.stat('file.txt', (err, stats) => {
if (err) {
console.error('获取文件信息错误:', err);
return;
}
console.log('文件信息:');
console.log(`- 是文件? ${stats.isFile()}`);
console.log(`- 是目录? ${stats.isDirectory()}`);
console.log(`- 文件大小: ${stats.size} 字节`);
console.log(`- 创建时间: ${stats.birthtime}`);
console.log(`- 修改时间: ${stats.mtime}`);
});
4.4 文件路径操作
const path = require('path');
// 路径拼接 (跨平台兼容)
const fullPath = path.join(__dirname, 'subfolder', 'file.txt');
console.log('拼接路径:', fullPath);
// 解析路径
const pathInfo = path.parse('/home/user/documents/file.txt');
console.log('路径信息:', pathInfo);
/*
输出:
{
root: '/',
dir: '/home/user/documents',
base: 'file.txt',
ext: '.txt',
name: 'file'
}
*/
// 规范化路径
console.log('规范化路径:', path.normalize('/home//user/../user/docs/'));
// 输出: /home/user/docs/
// 获取绝对路径
console.log('绝对路径:', path.resolve('subfolder', 'file.txt'));
// 获取扩展名
console.log('扩展名:', path.extname('file.txt')); // 输出: .txt
5. HTTP服务器开发
5.1 创建基本HTTP服务器
const http = require('http');
// 创建HTTP服务器
const server = http.createServer((req, res) => {
// 获取请求信息
const { method, url, headers } = req;
console.log(`收到 ${method} 请求: ${url}`);
// 根据URL路径提供不同响应
if (url === '/') {
// 设置响应头
res.writeHead(200, {'Content-Type': 'text/html'});
// 发送响应数据
res.end(`
<html>
<head><title>Node.js服务器</title></head>
<body>
<h1>欢迎来到Node.js服务器!</h1>
<p>当前时间: ${new Date().toLocaleString()}</p>
<ul>
<li><a href="/about">关于我们</a></li>
<li><a href="/contact">联系我们</a></li>
</ul>
</body>
</html>
`);
}
else if (url === '/about') {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(`
<html>
<head><title>关于我们</title></head>
<body>
<h1>关于我们</h1>
<p>这是一个简单的Node.js HTTP服务器示例。</p>
<a href="/">返回首页</a>
</body>
</html>
`);
}
else if (url === '/contact') {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(`
<html>
<head><title>联系我们</title></head>
<body>
<h1>联系我们</h1>
<p>Email: example@example.com</p>
<a href="/">返回首页</a>
</body>
</html>
`);
}
else if (url === '/api/time') {
// 返回JSON数据
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({
time: new Date().toISOString(),
timestamp: Date.now()
}));
}
else {
// 404 Not Found
res.writeHead(404, {'Content-Type': 'text/html'});
res.end(`
<html>
<head><title>404 Not Found</title></head>
<body>
<h1>404 - 页面不存在</h1>
<p>请求的URL "${url}" 不存在</p>
<a href="/">返回首页</a>
</body>
</html>
`);
}
});
// 服务器监听端口
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}/`);
});
5.2 处理HTTP请求和路由
const http = require('http');
const url = require('url');
// 路由处理函数
const routes = {
'GET': {
'/': (req, res) => {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('<h1>首页</h1><p>欢迎访问我们的网站</p>');
},
'/about': (req, res) => {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('<h1>关于我们</h1><p>这是关于页面</p>');
},
'/api/users': (req, res) => {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(users));
}
},
'POST': {
'/api/users': (req, res) => {
let body = '';
// 收集请求体数据
req.on('data', chunk => {
body += chunk.toString();
});
// 请求体接收完毕
req.on('end', () => {
try {
const userData = JSON.parse(body);
// 处理用户数据...
res.writeHead(201, {'Content-Type': 'application/json'});
res.end(JSON.stringify({
message: '用户创建成功',
user: userData
}));
} catch (error) {
res.writeHead(400, {'Content-Type': 'application/json'});
res.end(JSON.stringify({
error: '无效的JSON数据'
}));
}
});
}
}
};
// 创建HTTP服务器
const server = http.createServer((req, res) => {
// 解析URL和查询参数
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const method = req.method.toUpperCase();
const query = parsedUrl.query;
console.log(`${method} ${path}`);
// 查找路由处理函数
const routeHandler = routes[method] && routes[method][path];
if (routeHandler) {
// 将查询参数附加到请求对象
req.query = query;
// 调用路由处理函数
routeHandler(req, res);
} else {
// 未找到路由处理函数,返回404
res.writeHead(404, {'Content-Type': 'text/html'});
res.end('<h1>404 - 页面不存在</h1>');
}
});
// 启动服务器
const PORT = 3000;
server.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}/`);
});
5.3 处理静态文件
const http = require('http');
const fs = require('fs');
const path = require('path');
// 静态文件目录
const PUBLIC_DIR = path.join(__dirname, 'public');
// MIME类型映射
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.txt': 'text/plain'
};
// 创建HTTP服务器
const server = http.createServer((req, res) => {
// 仅处理GET请求
if (req.method !== 'GET') {
res.writeHead(405, {'Content-Type': 'text/plain'});
res.end('Method Not Allowed');
return;
}
// 获取请求路径
let filePath = path.join(PUBLIC_DIR, req.url === '/' ? 'index.html' : req.url);
// 检查文件是否存在
fs.stat(filePath, (err, stats) => {
if (err) {
// 文件不存在,返回404
res.writeHead(404, {'Content-Type': 'text/html'});
res.end('<h1>404 - 文件未找到</h1>');
return;
}
// 如果是目录,尝试加载index.html
if (stats.isDirectory()) {
filePath = path.join(filePath, 'index.html');
}
// 获取文件扩展名
const extname = path.extname(filePath);
// 获取MIME类型
const contentType = MIME_TYPES[extname] || 'application/octet-stream';
// 读取并发送文件
fs.readFile(filePath, (err, content) => {
if (err) {
if (err.code === 'ENOENT') {
// 文件不存在
res.writeHead(404, {'Content-Type': 'text/html'});
res.end('<h1>404 - 文件未找到</h1>');
} else {
// 服务器错误
res.writeHead(500, {'Content-Type': 'text/html'});
res.end('<h1>500 - 服务器错误</h1>');
}
} else {
// 发送文件
res.writeHead(200, {'Content-Type': contentType});
res.end(content);
}
});
});
});
// 启动服务器
const PORT = 3000;
server.listen(PORT, () => {
console.log(`静态文件服务器运行在 http://localhost:${PORT}/`);
console.log(`提供目录: ${PUBLIC_DIR}`);
});
结语
感谢您的阅读!期待您的一键三连!欢迎指正!