《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux... 。

文章目录
- 一、本文面试题目录
-
-
- [16. Node.js的核心模块有哪些?请举例说明其用途。](#16. Node.js的核心模块有哪些?请举例说明其用途。)
- [17. `fs`模块的作用是什么?它的同步和异步方法有什么区别?](#17.
fs
模块的作用是什么?它的同步和异步方法有什么区别?) - [18. 如何使用`fs`模块读取大文件?为什么不推荐用同步方法读取大文件?](#18. 如何使用
fs
模块读取大文件?为什么不推荐用同步方法读取大文件?) - [19. `path`模块的常用方法有哪些?它们的作用是什么?](#19.
path
模块的常用方法有哪些?它们的作用是什么?) - [20. `http`模块如何创建一个简单的Web服务器?](#20.
http
模块如何创建一个简单的Web服务器?) - [21. 如何处理`http`请求中的GET和POST参数?](#21. 如何处理
http
请求中的GET和POST参数?) - [22. `url`模块的作用是什么?如何解析URL字符串?](#22.
url
模块的作用是什么?如何解析URL字符串?) - [23. `querystring`模块如何解析查询字符串?](#23.
querystring
模块如何解析查询字符串?) - [24. `events`模块的作用是什么?如何自定义事件?](#24.
events
模块的作用是什么?如何自定义事件?) - [25. 什么是EventEmitter?它的常用方法有哪些?](#25. 什么是EventEmitter?它的常用方法有哪些?)
- [26. `stream`模块的作用是什么?它有哪几种类型?](#26.
stream
模块的作用是什么?它有哪几种类型?) - [27. 为什么说流(Stream)适合处理大文件?请举例说明流的使用场景。](#27. 为什么说流(Stream)适合处理大文件?请举例说明流的使用场景。)
- [28. `buffer`模块的作用是什么?它和字符串有什么区别?](#28.
buffer
模块的作用是什么?它和字符串有什么区别?) - [29. 如何在Node.js中操作Buffer?请举例说明常用方法。](#29. 如何在Node.js中操作Buffer?请举例说明常用方法。)
-
- [1. 创建Buffer](#1. 创建Buffer)
- [2. 读写Buffer](#2. 读写Buffer)
- [3. 复制Buffer](#3. 复制Buffer)
- [4. 切片Buffer](#4. 切片Buffer)
- [5. 拼接Buffer](#5. 拼接Buffer)
- [6. 比较Buffer](#6. 比较Buffer)
- [30. `util`模块的`util.promisify`方法有什么作用?](#30.
util
模块的util.promisify
方法有什么作用?)
-
- 二、120道Node.js面试题目录列表
一、本文面试题目录
16. Node.js的核心模块有哪些?请举例说明其用途。
Node.js 提供了众多核心模块,这些模块由 Node.js 官方开发,无需额外安装即可使用,主要用于处理文件、网络、事件等基础功能。以下是常用核心模块及其用途:
fs
:文件系统模块,用于读写文件、操作目录等(如fs.readFile
读取文件,fs.writeFile
写入文件)。path
:路径处理模块,用于解析和拼接文件路径(如path.join
拼接路径,path.resolve
获取绝对路径)。http
:HTTP 协议模块,用于创建 Web 服务器和客户端(如http.createServer
创建服务器)。https
:HTTPS 协议模块,功能类似http
,但支持加密传输。url
:URL 解析模块,用于解析 URL 字符串(如url.parse
解析 URL 为对象)。querystring
:查询字符串模块,用于解析和序列化 URL 中的查询参数(如querystring.parse
解析查询字符串)。events
:事件处理模块,提供事件触发和监听机制(核心是EventEmitter
类)。stream
:流模块,用于处理流式数据(如文件流、网络流),适合大文件处理。buffer
:缓冲区模块,用于处理二进制数据(如网络传输、文件读写中的二进制数据)。util
:工具模块,提供实用工具函数(如util.promisify
将回调函数转为 Promise)。os
:操作系统模块,用于获取系统信息(如os.cpus
获取 CPU 信息,os.totalmem
获取总内存)。process
:进程模块,提供当前 Node.js 进程的信息和控制方法(如process.env
环境变量,process.exit
退出进程)。
示例:使用多个核心模块
javascript
const fs = require('fs');
const path = require('path');
const os = require('os');
// 读取当前目录下的文件
const filePath = path.join(__dirname, 'example.txt');
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) throw err;
console.log('文件内容:', data);
});
// 输出系统信息
console.log('CPU 核心数:', os.cpus().length);
console.log('总内存(GB):', os.totalmem() / (1024 **3).toFixed(2));
17. fs
模块的作用是什么?它的同步和异步方法有什么区别?
fs
(File System)模块是 Node.js 用于与文件系统交互的核心模块,提供了文件读写、目录操作、权限管理等功能。它的方法分为同步 和异步两种形式。
同步方法与异步方法的区别:
区别 | 同步方法 | 异步方法 |
---|---|---|
执行方式 | 阻塞主线程,需等待操作完成后才继续执行 | 非阻塞主线程,操作在后台执行,完成后通过回调通知 |
命名规则 | 方法名末尾加 Sync (如 readFileSync ) |
方法名无特殊后缀(如 readFile ) |
错误处理 | 通过 try/catch 捕获错误 |
通过回调函数的第一个参数返回错误 |
返回值 | 直接返回操作结果 | 结果通过回调函数的参数返回 |
适用场景 | 简单脚本、初始化操作(不影响并发的场景) | 服务器环境、高并发场景(避免阻塞主线程) |
示例:同步与异步读取文件
javascript
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'test.txt');
// 同步读取
try {
const data = fs.readFileSync(filePath, 'utf8');
console.log('同步读取结果:', data);
} catch (err) {
console.error('同步读取错误:', err);
}
// 异步读取
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error('异步读取错误:', err);
return;
}
console.log('异步读取结果:', data);
});
18. 如何使用fs
模块读取大文件?为什么不推荐用同步方法读取大文件?
读取大文件(如几个 GB 的文件)时,不适合一次性将文件内容加载到内存中,而应使用流(Stream) 进行分块读取,以降低内存占用。
使用流读取大文件的示例:
javascript
const fs = require('fs');
const path = require('path');
const largeFilePath = path.join(__dirname, 'large-file.txt');
// 创建可读流
const readStream = fs.createReadStream(largeFilePath, {
encoding: 'utf8', // 编码格式
highWaterMark: 64 * 1024 // 每次读取的缓冲区大小(默认64KB)
});
// 监听数据事件(每读取一块数据触发)
readStream.on('data', (chunk) => {
console.log(`读取到 ${chunk.length} 字节的数据`);
// 处理数据(如解析、过滤等)
});
// 监听结束事件
readStream.on('end', () => {
console.log('文件读取完成');
});
// 监听错误事件
readStream.on('error', (err) => {
console.error('读取错误:', err);
});
不推荐用同步方法读取大文件的原因 :
1.** 阻塞主线程 :同步方法(如 readFileSync
)会阻塞 Node.js 主线程,直到文件读取完成,期间无法处理其他请求,严重影响应用的并发能力。
2. 内存占用过高 :同步方法会将整个文件内容加载到内存中,大文件可能导致内存溢出(OOM),使应用崩溃。
3. 性能低下**:一次性读取大文件需要分配大量内存,且数据处理效率低,而流的分块处理更轻量、高效。
19. path
模块的常用方法有哪些?它们的作用是什么?
path
模块用于处理文件路径,解决不同操作系统(如 Windows 和 Linux)路径分隔符差异(\
和 /
)的问题,提供了跨平台的路径处理能力。
常用方法及作用:
-
path.join([...paths])
:- 拼接多个路径片段,自动处理分隔符,返回规范化的路径。
javascriptpath.join('/a', 'b', 'c'); // 输出:'/a/b/c' path.join('/a', '../b'); // 输出:'/b'(解析上层目录)
-
path.resolve([...paths])
:- 将路径片段解析为绝对路径,以当前工作目录为基准。
javascriptpath.resolve('file.txt'); // 输出:'/当前工作目录/file.txt' path.resolve('/a', 'b', '../c'); // 输出:'/a/c'
-
path.basename(path[, ext])
:- 返回路径中的文件名(包含扩展名),可选参数
ext
可去除扩展名。
javascriptpath.basename('/a/b/c.txt'); // 输出:'c.txt' path.basename('/a/b/c.txt', '.txt'); // 输出:'c'
- 返回路径中的文件名(包含扩展名),可选参数
-
path.dirname(path)
:- 返回路径中的目录部分。
javascriptpath.dirname('/a/b/c.txt'); // 输出:'/a/b'
-
path.extname(path)
:- 返回路径中的文件扩展名(包含
.
),无扩展名则返回空字符串。
javascriptpath.extname('file.txt'); // 输出:'.txt' path.extname('file'); // 输出:'' path.extname('file.md.txt');// 输出:'.txt'
- 返回路径中的文件扩展名(包含
-
path.parse(path)
:- 将路径解析为包含
root
、dir
、base
、name
、ext
的对象。
javascriptpath.parse('/a/b/c.txt'); // 输出: // { // root: '/', // dir: '/a/b', // base: 'c.txt', // name: 'c', // ext: '.txt' // }
- 将路径解析为包含
-
path.format(pathObject)
:- 将
path.parse
生成的对象转换为路径字符串。
javascriptpath.format({ dir: '/a/b', base: 'c.txt' }); // 输出:'/a/b/c.txt'
- 将
示例 :综合使用 path
模块
javascript
const path = require('path');
const filePath = '/user/docs/report.pdf';
console.log('文件名:', path.basename(filePath)); // 'report.pdf'
console.log('目录:', path.dirname(filePath)); // '/user/docs'
console.log('扩展名:', path.extname(filePath)); // '.pdf'
console.log('拼接路径:', path.join(path.dirname(filePath), 'archive', 'old.pdf'));
// '/user/docs/archive/old.pdf'
20. http
模块如何创建一个简单的Web服务器?
http
模块是 Node.js 用于构建 HTTP 服务器和客户端的核心模块,通过 http.createServer()
方法可快速创建 Web 服务器。
创建简单Web服务器的步骤:
- 引入
http
模块。 - 使用
http.createServer()
创建服务器实例,传入请求处理函数(接收req
请求对象和res
响应对象)。 - 调用
server.listen()
方法指定端口和主机,启动服务器。
示例:
javascript
const http = require('http');
// 创建服务器
const server = http.createServer((req, res) => {
// 设置响应头(状态码 200,内容类型为文本)
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
// 根据 URL 路径返回不同内容
if (req.url === '/') {
res.end('欢迎访问首页!\n');
} else if (req.url === '/about') {
res.end('关于我们\n');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 页面未找到\n');
}
});
// 启动服务器,监听 3000 端口
const PORT = 3000;
server.listen(PORT, 'localhost', () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
关键对象说明:
req
(IncomingMessage):请求对象,包含请求方法、URL、头信息等(如req.method
、req.url
)。res
(ServerResponse):响应对象,用于发送响应(如res.writeHead()
设置响应头,res.end()
发送响应体并结束响应)。
运行上述代码后,访问 http://localhost:3000
可看到首页内容,访问 http://localhost:3000/about
可看到关于页面。
21. 如何处理http
请求中的GET和POST参数?
在 Node.js 的 http
模块中,GET 和 POST 参数的处理方式不同:GET 参数位于 URL 中,POST 参数位于请求体中。
处理GET参数
GET 参数通过 URL 的查询字符串(?key=value&key2=value2
)传递,可使用 url
模块或 querystring
模块解析。
示例:
javascript
const http = require('http');
const url = require('url');
const querystring = require('querystring');
const server = http.createServer((req, res) => {
if (req.method === 'GET') {
// 解析 URL(第二个参数为 true 时自动解析 query 为对象)
const parsedUrl = url.parse(req.url, true);
const query = parsedUrl.query; // 获取 GET 参数对象
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'GET 请求参数',
params: query
}));
}
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
访问 http://localhost:3000?name=Alice&age=20
,将返回解析后的 GET 参数。
处理POST参数
POST 参数位于请求体中,需通过监听 req
对象的 data
事件(接收数据块)和 end
事件(数据接收完成)来获取,再用 querystring
解析。
示例:
javascript
const http = require('http');
const querystring = require('querystring');
const server = http.createServer((req, res) => {
if (req.method === 'POST') {
let body = '';
// 接收数据块
req.on('data', (chunk) => {
body += chunk;
});
// 数据接收完成
req.on('end', () => {
// 解析 POST 参数(默认解析 form-data 格式)
const postData = querystring.parse(body);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'POST 请求参数',
params: postData
}));
});
}
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
可通过 POST 工具(如 Postman)发送 name=Bob&age=25
表单数据,服务器将返回解析结果。
注意 :对于 JSON 格式的 POST 数据(如 {"name":"Bob"}
),需手动解析:
javascript
// 在 end 事件中
const postData = JSON.parse(body); // 适用于 Content-Type: application/json
22. url
模块的作用是什么?如何解析URL字符串?
url
模块用于处理 URL 字符串,提供了解析 URL、格式化 URL 等功能,可将 URL 字符串转换为对象(便于获取各部分信息),或反之。
主要作用:
- 解析 URL 字符串为对象,提取协议、主机、路径、查询参数等部分。
- 将 URL 对象格式化为 URL 字符串。
- 处理不同格式的 URL(如绝对 URL、相对 URL)。
解析URL字符串的方法 :
使用 url.parse(urlString[, parseQueryString[, slashesDenoteHost]])
方法:
urlString
:要解析的 URL 字符串。parseQueryString
:可选,默认为false
,若为true
,则将查询字符串解析为对象。slashesDenoteHost
:可选,默认为false
,用于处理无协议的 URL。
示例:
javascript
const url = require('url');
const urlString = 'https://user:pass@example.com:8080/path?name=Alice&age=20#hash';
// 解析 URL(parseQueryString 设为 true,自动解析查询参数)
const parsedUrl = url.parse(urlString, true);
console.log('解析结果:', parsedUrl);
/* 输出主要属性:
{
protocol: 'https:', // 协议
auth: 'user:pass', // 用户名:密码
host: 'example.com:8080', // 主机名:端口
hostname: 'example.com', // 主机名
port: '8080', // 端口
pathname: '/path', // 路径
search: '?name=Alice&age=20', // 查询字符串(含?)
query: { name: 'Alice', age: '20' }, // 解析后的查询参数对象
hash: '#hash' // 哈希(含#)
}
*/
// 将 URL 对象转换为字符串
const formattedUrl = url.format(parsedUrl);
console.log('格式化结果:', formattedUrl); // 输出原始 URL 字符串
注意 :Node.js v11.0.0 后推荐使用 WHATWG URL 标准 API(new URL(urlString)
),功能更完善:
javascript
const myUrl = new URL('https://example.com/path?name=Alice');
console.log(myUrl.searchParams.get('name')); // 'Alice'(获取查询参数)
23. querystring
模块如何解析查询字符串?
querystring
模块用于处理 URL 中的查询字符串(如 name=Alice&age=20
),提供了解析(字符串转对象)和序列化(对象转字符串)功能。
常用方法:
-
querystring.parse(str[, sep[, eq[, options]]])
:- 解析查询字符串为对象。
str
:要解析的查询字符串。sep
:分隔符,默认为&
。eq
:键值分隔符,默认为=
。options
:可选配置(如maxKeys
限制解析的键数量)。
示例:
javascriptconst querystring = require('querystring'); const str = 'name=Alice&age=20&hobby=reading&hobby=sports'; const obj = querystring.parse(str); console.log(obj); // 输出:{ name: 'Alice', age: '20', hobby: [ 'reading', 'sports' ] }
-
querystring.stringify(obj[, sep[, eq[, options]]])
:- 将对象序列化为查询字符串。
obj
:要序列化的对象。sep
:分隔符,默认为&
。eq
:键值分隔符,默认为=
。
示例:
javascriptconst querystring = require('querystring'); const obj = { name: 'Bob', age: 25, hobby: ['music', 'games'] }; const str = querystring.stringify(obj); console.log(str); // 输出:'name=Bob&age=25&hobby=music&hobby=games'
-
querystring.escape(str)
:- 对字符串进行 URL 编码(将特殊字符转换为 %xx 格式)。
javascriptquerystring.escape('a b=c'); // 输出:'a%20b%3Dc'
-
querystring.unescape(str)
:- 对 URL 编码的字符串进行解码。
javascriptquerystring.unescape('a%20b%3Dc'); // 输出:'a b=c'
注意 :querystring
适合解析简单的查询字符串,对于复杂场景(如嵌套对象),建议使用 qs
等第三方库。
24. events
模块的作用是什么?如何自定义事件?
events
模块是 Node.js 实现事件驱动架构的核心,提供了事件的触发、监听、移除等功能,其核心是 EventEmitter
类。
主要作用:
- 实现对象间的事件通信(发布-订阅模式)。
- 支持自定义事件,通过触发事件执行回调函数。
- 是许多 Node.js 核心模块(如
fs
、http
)的基础(这些模块的对象继承自EventEmitter
)。
自定义事件的步骤:
- 引入
events
模块,创建EventEmitter
实例。 - 使用
on()
或addListener()
方法监听事件。 - 使用
emit()
方法触发事件,可传递参数。
示例:
javascript
const EventEmitter = require('events');
// 创建 EventEmitter 实例
const myEmitter = new EventEmitter();
// 监听 'greet' 事件(第一个参数为事件名,第二个为回调函数)
myEmitter.on('greet', (name) => {
console.log(`Hello, ${name}!`);
});
// 监听 'sum' 事件,处理数字相加
myEmitter.on('sum', (a, b) => {
console.log(`${a} + ${b} = ${a + b}`);
});
// 触发事件(第一个参数为事件名,后续为传递给回调的参数)
myEmitter.emit('greet', 'Alice'); // 输出:Hello, Alice!
myEmitter.emit('sum', 3, 5); // 输出:3 + 5 = 8
// 一次性事件(触发后自动移除)
myEmitter.once('single', () => {
console.log('这个事件只会触发一次');
});
myEmitter.emit('single'); // 输出:这个事件只会触发一次
myEmitter.emit('single'); // 无输出(事件已移除)
注意 :EventEmitter
有默认的事件监听器数量限制(默认 10 个),超过时会触发警告,可通过 setMaxListeners(n)
调整。
25. 什么是EventEmitter?它的常用方法有哪些?
EventEmitter
是 events
模块提供的一个类,是 Node.js 事件驱动模型的核心,用于实现事件的发布(触发)和订阅(监听)。Node.js 中的许多核心对象(如 http.Server
、fs.ReadStream
)都继承自 EventEmitter
。
常用方法:
-
on(eventName, listener)
:- 为指定事件注册一个监听器(回调函数),可多次调用注册多个监听器。
javascriptemitter.on('data', (chunk) => { console.log(chunk); });
-
emit(eventName[, ...args])
:- 触发指定事件,可传递多个参数给监听器。返回
true
表示事件有监听器,否则为false
。
javascriptemitter.emit('data', 'hello'); // 触发 'data' 事件,传递 'hello'
- 触发指定事件,可传递多个参数给监听器。返回
-
once(eventName, listener)
:- 为指定事件注册一个一次性监听器,触发一次后自动移除。
javascriptemitter.once('init', () => { console.log('初始化完成'); });
-
off(eventName, listener)
(或removeListener
):- 移除指定事件的某个监听器(需传入监听器的引用)。
javascriptconst listener = () => { console.log('监听'); }; emitter.on('event', listener); emitter.off('event', listener); // 移除监听器
-
removeAllListeners([eventName])
:- 移除指定事件的所有监听器,若未指定事件名,则移除所有事件的监听器。
javascriptemitter.removeAllListeners('data'); // 移除 'data' 事件的所有监听器
-
setMaxListeners(n)
:- 设置事件监听器的最大数量(默认 10 个),超过时的警告可通过此方法关闭(设为
Infinity
)。
javascriptemitter.setMaxListeners(20); // 允许最多 20 个监听器
- 设置事件监听器的最大数量(默认 10 个),超过时的警告可通过此方法关闭(设为
-
listeners(eventName)
:- 返回指定事件的所有监听器数组。
javascriptconsole.log(emitter.listeners('data')); // 输出 'data' 事件的监听器数组
示例 :综合使用 EventEmitter
方法
javascript
const EventEmitter = require('events');
const emitter = new EventEmitter();
// 定义监听器函数
const logMessage = (msg) => {
console.log('收到消息:', msg);
};
// 注册监听器
emitter.on('message', logMessage);
emitter.on('message', (msg) => {
console.log('消息长度:', msg.length);
});
// 触发事件
emitter.emit('message', 'Hello EventEmitter');
// 输出:
// 收到消息: Hello EventEmitter
// 消息长度: 18
// 移除监听器
emitter.off('message', logMessage);
emitter.emit('message', '再次发送');
// 输出:
// 消息长度: 4
26. stream
模块的作用是什么?它有哪几种类型?
stream
(流)模块是 Node.js 用于处理流式数据的核心模块,通过分块读取/写入数据,降低内存占用,提高处理效率,尤其适合大文件或持续的数据流(如网络传输)。
主要作用:
- 分块处理数据,避免一次性加载大量数据到内存。
- 支持管道(
pipe
)操作,可将一个流的输出直接作为另一个流的输入(如文件压缩、网络传输)。 - 提供统一的接口处理不同类型的数据流(文件、网络、内存等)。
流的四种类型:
-
Readable(可读流):
- 用于读取数据的流(如
fs.createReadStream
读取文件)。 - 状态:
paused
(暂停)和flowing
(流动),数据通过data
事件传递。
- 用于读取数据的流(如
-
Writable(可写流):
- 用于写入数据的流(如
fs.createWriteStream
写入文件)。 - 通过
write()
方法写入数据,end()
方法结束写入。
- 用于写入数据的流(如
-
Duplex(双工流):
- 同时具备可读和可写能力的流(如 TCP 套接字
net.Socket
)。 - 可读和可写部分相互独立,分别遵循各自的接口。
- 同时具备可读和可写能力的流(如 TCP 套接字
-
Transform(转换流):
- 一种特殊的双工流,用于修改或转换数据(如
zlib.createGzip
压缩数据)。 - 读取数据后进行处理,再输出处理后的结果。
- 一种特殊的双工流,用于修改或转换数据(如
示例:使用可读流和可写流复制文件
javascript
const fs = require('fs');
const path = require('path');
// 创建可读流(源文件)
const readStream = fs.createReadStream(path.join(__dirname, 'source.txt'));
// 创建可写流(目标文件)
const writeStream = fs.createWriteStream(path.join(__dirname, 'copy.txt'));
// 监听数据事件,将读取的块写入可写流
readStream.on('data', (chunk) => {
writeStream.write(chunk);
});
// 读取完成后结束写入
readStream.on('end', () => {
writeStream.end();
console.log('文件复制完成');
});
// 简化写法:使用 pipe 自动处理数据传递
// readStream.pipe(writeStream);
27. 为什么说流(Stream)适合处理大文件?请举例说明流的使用场景。
流(Stream)适合处理大文件的核心原因是其分块处理数据的特性,无需将整个文件加载到内存中,而是逐块读取、处理和写入,显著降低内存占用并提高效率。
流适合处理大文件的原因:
- 低内存占用:大文件(如 10GB)无法一次性加载到内存,流通过每次读取一小块数据(默认 64KB),内存占用保持稳定。
- 高效处理:数据可边读取边处理(如解析、过滤),无需等待整个文件加载完成,减少处理延迟。
- 管道操作 :支持
pipe()
方法将多个流串联(如读取 → 压缩 → 写入),简化大文件处理流程。
流的典型使用场景:
1.** 大文件复制 **:
javascript
const fs = require('fs');
// 使用 pipe 快速复制大文件
fs.createReadStream('large-file.iso').pipe(fs.createWriteStream('copy-large-file.iso'));
2.** 日志实时处理 **:
监听日志文件的新增内容并实时分析(如监控系统)。
javascript
const fs = require('fs');
const readStream = fs.createReadStream('app.log', { tail: true }); // 模拟尾行监听
readStream.on('data', (chunk) => {
console.log('新日志:', chunk.toString());
// 实时分析逻辑(如错误检测)
});
3.** 数据压缩/解压 **:
结合 zlib
模块对流数据进行压缩(无需加载整个文件到内存)。
javascript
const fs = require('fs');
const zlib = require('zlib');
// 读取文件 → 压缩 → 写入压缩文件
fs.createReadStream('large-file.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('large-file.txt.gz'));
4.** 网络数据传输 **:
HTTP 服务器中通过流发送大文件(如视频、下载资源)。
javascript
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
if (req.url === '/video') {
res.writeHead(200, { 'Content-Type': 'video/mp4' });
// 以流的形式发送视频文件
fs.createReadStream('large-video.mp4').pipe(res);
}
}).listen(3000);
5.** 数据转换 **:
处理 CSV 大文件并转换为 JSON 格式。
javascript
const fs = require('fs');
const readline = require('readline'); // 逐行读取流
const rl = readline.createInterface({
input: fs.createReadStream('large-data.csv'),
crlfDelay: Infinity
});
rl.on('line', (line) => {
// 每行转换为 JSON 并处理
const json = convertCsvToJson(line);
console.log(json);
});
28. buffer
模块的作用是什么?它和字符串有什么区别?
buffer
模块是 Node.js 用于处理二进制数据的核心模块,提供了 Buffer
类(无需 require
即可使用),用于存储和操作二进制数据。在处理文件、网络传输、加密等场景中,二进制数据是基础,Buffer
解决了 JavaScript 原生不支持二进制数据的问题。
Buffer
与字符串的区别:
区别 | Buffer | 字符串(String) |
---|---|---|
数据类型 | 二进制数据(字节序列) | Unicode 字符序列(文本数据) |
编码方式 | 可通过编码(如 utf8 、base64 )转换为字符串 |
本质是 Unicode 编码,可通过 Buffer.from() 转为 Buffer |
长度计算 | length 属性返回字节数 |
length 属性返回字符数(可能与字节数不同) |
不可变性 | 长度固定,内容可修改(内存分配后大小不变) | 不可变,修改会创建新字符串 |
适用场景 | 二进制数据(文件、网络、加密) | 文本数据(用户输入、显示内容) |
示例:Buffer 与字符串的转换
javascript
// 字符串转 Buffer(默认 utf8 编码)
const str = 'Hello 世界';
const buf = Buffer.from(str);
console.log('Buffer 内容:', buf); // <Buffer 48 65 6c 6c 6f 20 e4 b8 96 e7 95 8c>
console.log('Buffer 字节数:', buf.length); // 11('Hello ' 占 6 字节,'世界' 占 6 字节?此处实际为11字节,因UTF8编码中汉字占3字节)
// Buffer 转字符串
const str2 = buf.toString('utf8');
console.log('转换后的字符串:', str2); // 'Hello 世界'
// 不同编码的转换
const base64Str = buf.toString('base64');
console.log('Base64 编码:', base64Str); // 'SGVsbG8g5LiW55WM'
const buf2 = Buffer.from(base64Str, 'base64');
console.log(buf2.toString('utf8')); // 'Hello 世界'
注意 :字符串的 length
可能与对应的 Buffer 字节数不同(如中文在 UTF8 中占 3 字节),例如 '世界'.length
为 2,而 Buffer.from('世界').length
为 6。
29. 如何在Node.js中操作Buffer?请举例说明常用方法。
Buffer
是 Node.js 中处理二进制数据的核心类,提供了丰富的方法用于创建、读写、转换 Buffer。以下是常用操作及示例:
1. 创建Buffer
Buffer.alloc(size)
:创建指定大小的空 Buffer(已初始化,默认填充 0)。Buffer.allocUnsafe(size)
:创建指定大小的 Buffer(未初始化,可能包含旧数据,速度更快)。Buffer.from(data)
:从数据(字符串、数组、Buffer 等)创建 Buffer。
javascript
// 创建 10 字节的空 Buffer
const buf1 = Buffer.alloc(10);
console.log(buf1); // <Buffer 00 00 00 00 00 00 00 00 00 00>
// 从字符串创建 Buffer
const buf2 = Buffer.from('hello');
console.log(buf2); // <Buffer 68 65 6c 6c 6f>
// 从数组创建 Buffer(数组元素为 0-255 的整数)
const buf3 = Buffer.from([0x68, 0x65, 0x6c]); // 对应 'hel'
console.log(buf3.toString()); // 'hel'
2. 读写Buffer
Buffer 可通过索引读写字节(0-255 的整数)。
javascript
const buf = Buffer.alloc(4);
// 写入字节
buf[0] = 0x48; // 'H' 的 ASCII 码
buf[1] = 0x65; // 'e'
buf[2] = 0x6c; // 'l'
buf[3] = 0x6c; // 'l'
console.log(buf.toString()); // 'Hell'
// 读取字节
console.log(buf[0]); // 72(0x48 的十进制)
console.log(String.fromCharCode(buf[0])); // 'H'
3. 复制Buffer
buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])
:复制一个 Buffer 的内容到另一个 Buffer。
javascript
const srcBuf = Buffer.from('hello');
const destBuf = Buffer.alloc(5);
srcBuf.copy(destBuf); // 复制整个 srcBuf 到 destBuf
console.log(destBuf.toString()); // 'hello'
// 部分复制(复制 'llo' 到 destBuf 的起始位置 2)
const destBuf2 = Buffer.alloc(5, 'he'); // 初始化为 'he'
srcBuf.copy(destBuf2, 2, 2, 5); // targetStart=2, sourceStart=2, sourceEnd=5
console.log(destBuf2.toString()); // 'hello'
4. 切片Buffer
buf.slice([start[, end]])
:返回 Buffer 的一部分(与原 Buffer 共享内存,修改会影响原 Buffer)。
javascript
const buf = Buffer.from('hello world');
const slice = buf.slice(6, 11); // 从索引 6 到 10(不包含11)
console.log(slice.toString()); // 'world'
// 修改切片会影响原 Buffer
slice[0] = 0x57; // 'W' 的 ASCII 码
console.log(buf.toString()); // 'hello World'
5. 拼接Buffer
Buffer.concat(list[, totalLength])
:拼接多个 Buffer 为一个新 Buffer。
javascript
const buf1 = Buffer.from('Hello ');
const buf2 = Buffer.from('World');
const combined = Buffer.concat([buf1, buf2]);
console.log(combined.toString()); // 'Hello World'
6. 比较Buffer
buf.compare(otherBuffer)
:比较两个 Buffer,返回数值(-1:当前小,0:相等,1:当前大)。
javascript
const buf1 = Buffer.from('abc');
const buf2 = Buffer.from('abd');
console.log(buf1.compare(buf2)); // -1('abc' < 'abd')
30. util
模块的util.promisify
方法有什么作用?
util.promisify
是 Node.js util
模块提供的工具函数,用于将遵循 Node.js 回调风格 (即最后一个参数为回调函数,且回调的第一个参数为错误对象)的函数转换为返回 Promise 的函数,便于使用 async/await
语法处理异步操作。
作用:
- 简化异步代码,将回调地狱(Callback Hell)转换为更易读的
async/await
形式。 - 统一异步编程风格,兼容 Promise 生态。
使用条件 :
被转换的函数必须符合 Node.js 回调规范:
- 函数的最后一个参数是回调函数。
- 回调函数的第一个参数是错误对象(
err
),后续参数是结果。
示例:将回调风格的函数转为 Promise
javascript
const util = require('util');
const fs = require('fs');
// 回调风格的函数(fs.readFile)
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('回调方式读取:', data);
});
// 使用 util.promisify 转换为 Promise 风格
const readFileAsync = util.promisify(fs.readFile);
// 使用 Promise.then()
readFileAsync('example.txt', 'utf8')
.then(data => console.log('Promise 方式读取:', data))
.catch(err => console.error('错误:', err));
// 使用 async/await(更简洁)
async function readFile() {
try {
const data = await readFileAsync('example.txt', 'utf8');
console.log('async/await 方式读取:', data);
} catch (err) {
console.error('错误:', err);
}
}
readFile();
自定义回调函数的转换:
javascript
const util = require('util');
// 自定义回调风格函数(符合规范)
function fetchData(callback) {
setTimeout(() => {
const err = null;
const data = '模拟数据';
callback(err, data); // 第一个参数为 err,第二个为结果
}, 1000);
}
// 转换为 Promise 函数
const fetchDataAsync = util.promisify(fetchData);
// 使用 async/await
async function main() {
const data = await fetchDataAsync();
console.log(data); // '模拟数据'
}
main();
注意 :Node.js v10.0.0 后,许多核心模块提供了内置的 Promise 版本(如 fs.promises
),可直接使用,无需手动转换。