Node.js面试题及详细答案120题(16-30) -- 核心模块篇

发布于:2025-08-14 ⋅ 阅读:(17) ⋅ 点赞:(0)

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

前后端面试题-专栏总目录

在这里插入图片描述

一、本文面试题目录

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 退出进程)。

示例:使用多个核心模块

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 捕获错误 通过回调函数的第一个参数返回错误
返回值 直接返回操作结果 结果通过回调函数的参数返回
适用场景 简单脚本、初始化操作(不影响并发的场景) 服务器环境、高并发场景(避免阻塞主线程)

示例:同步与异步读取文件

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) 进行分块读取,以降低内存占用。

使用流读取大文件的示例

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)路径分隔符差异(\/)的问题,提供了跨平台的路径处理能力。

常用方法及作用

  1. path.join([...paths])

    • 拼接多个路径片段,自动处理分隔符,返回规范化的路径。
    path.join('/a', 'b', 'c'); // 输出:'/a/b/c'
    path.join('/a', '../b');   // 输出:'/b'(解析上层目录)
    
  2. path.resolve([...paths])

    • 将路径片段解析为绝对路径,以当前工作目录为基准。
    path.resolve('file.txt');        // 输出:'/当前工作目录/file.txt'
    path.resolve('/a', 'b', '../c'); // 输出:'/a/c'
    
  3. path.basename(path[, ext])

    • 返回路径中的文件名(包含扩展名),可选参数 ext 可去除扩展名。
    path.basename('/a/b/c.txt');   // 输出:'c.txt'
    path.basename('/a/b/c.txt', '.txt'); // 输出:'c'
    
  4. path.dirname(path)

    • 返回路径中的目录部分。
    path.dirname('/a/b/c.txt'); // 输出:'/a/b'
    
  5. path.extname(path)

    • 返回路径中的文件扩展名(包含 .),无扩展名则返回空字符串。
    path.extname('file.txt');   // 输出:'.txt'
    path.extname('file');       // 输出:''
    path.extname('file.md.txt');// 输出:'.txt'
    
  6. path.parse(path)

    • 将路径解析为包含 rootdirbasenameext 的对象。
    path.parse('/a/b/c.txt');
    // 输出:
    // {
    //   root: '/',
    //   dir: '/a/b',
    //   base: 'c.txt',
    //   name: 'c',
    //   ext: '.txt'
    // }
    
  7. path.format(pathObject)

    • path.parse 生成的对象转换为路径字符串。
    path.format({ dir: '/a/b', base: 'c.txt' }); // 输出:'/a/b/c.txt'
    

示例:综合使用 path 模块

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服务器的步骤

  1. 引入 http 模块。
  2. 使用 http.createServer() 创建服务器实例,传入请求处理函数(接收 req 请求对象和 res 响应对象)。
  3. 调用 server.listen() 方法指定端口和主机,启动服务器。

示例

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.methodreq.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 模块解析。

示例

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 解析。

示例

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"}),需手动解析:

// 在 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。

示例

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)),功能更完善:

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),提供了解析(字符串转对象)和序列化(对象转字符串)功能。

常用方法

  1. querystring.parse(str[, sep[, eq[, options]]])

    • 解析查询字符串为对象。
    • str:要解析的查询字符串。
    • sep:分隔符,默认为 &
    • eq:键值分隔符,默认为 =
    • options:可选配置(如 maxKeys 限制解析的键数量)。

    示例

    const 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' ] }
    
  2. querystring.stringify(obj[, sep[, eq[, options]]])

    • 将对象序列化为查询字符串。
    • obj:要序列化的对象。
    • sep:分隔符,默认为 &
    • eq:键值分隔符,默认为 =

    示例

    const 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'
    
  3. querystring.escape(str)

    • 对字符串进行 URL 编码(将特殊字符转换为 %xx 格式)。
    querystring.escape('a b=c'); // 输出:'a%20b%3Dc'
    
  4. querystring.unescape(str)

    • 对 URL 编码的字符串进行解码。
    querystring.unescape('a%20b%3Dc'); // 输出:'a b=c'
    

注意querystring 适合解析简单的查询字符串,对于复杂场景(如嵌套对象),建议使用 qs 等第三方库。

24. events模块的作用是什么?如何自定义事件?

events 模块是 Node.js 实现事件驱动架构的核心,提供了事件的触发、监听、移除等功能,其核心是 EventEmitter 类。

主要作用

  • 实现对象间的事件通信(发布-订阅模式)。
  • 支持自定义事件,通过触发事件执行回调函数。
  • 是许多 Node.js 核心模块(如 fshttp)的基础(这些模块的对象继承自 EventEmitter)。

自定义事件的步骤

  1. 引入 events 模块,创建 EventEmitter 实例。
  2. 使用 on()addListener() 方法监听事件。
  3. 使用 emit() 方法触发事件,可传递参数。

示例

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?它的常用方法有哪些?

EventEmitterevents 模块提供的一个类,是 Node.js 事件驱动模型的核心,用于实现事件的发布(触发)和订阅(监听)。Node.js 中的许多核心对象(如 http.Serverfs.ReadStream)都继承自 EventEmitter

常用方法

  1. on(eventName, listener)

    • 为指定事件注册一个监听器(回调函数),可多次调用注册多个监听器。
    emitter.on('data', (chunk) => { console.log(chunk); });
    
  2. emit(eventName[, ...args])

    • 触发指定事件,可传递多个参数给监听器。返回 true 表示事件有监听器,否则为 false
    emitter.emit('data', 'hello'); // 触发 'data' 事件,传递 'hello'
    
  3. once(eventName, listener)

    • 为指定事件注册一个一次性监听器,触发一次后自动移除。
    emitter.once('init', () => { console.log('初始化完成'); });
    
  4. off(eventName, listener)(或 removeListener):

    • 移除指定事件的某个监听器(需传入监听器的引用)。
    const listener = () => { console.log('监听'); };
    emitter.on('event', listener);
    emitter.off('event', listener); // 移除监听器
    
  5. removeAllListeners([eventName])

    • 移除指定事件的所有监听器,若未指定事件名,则移除所有事件的监听器。
    emitter.removeAllListeners('data'); // 移除 'data' 事件的所有监听器
    
  6. setMaxListeners(n)

    • 设置事件监听器的最大数量(默认 10 个),超过时的警告可通过此方法关闭(设为 Infinity)。
    emitter.setMaxListeners(20); // 允许最多 20 个监听器
    
  7. listeners(eventName)

    • 返回指定事件的所有监听器数组。
    console.log(emitter.listeners('data')); // 输出 'data' 事件的监听器数组
    

示例:综合使用 EventEmitter 方法

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)操作,可将一个流的输出直接作为另一个流的输入(如文件压缩、网络传输)。
  • 提供统一的接口处理不同类型的数据流(文件、网络、内存等)。

流的四种类型

  1. Readable(可读流)

    • 用于读取数据的流(如 fs.createReadStream 读取文件)。
    • 状态:paused(暂停)和 flowing(流动),数据通过 data 事件传递。
  2. Writable(可写流)

    • 用于写入数据的流(如 fs.createWriteStream 写入文件)。
    • 通过 write() 方法写入数据,end() 方法结束写入。
  3. Duplex(双工流)

    • 同时具备可读和可写能力的流(如 TCP 套接字 net.Socket)。
    • 可读和可写部分相互独立,分别遵循各自的接口。
  4. Transform(转换流)

    • 一种特殊的双工流,用于修改或转换数据(如 zlib.createGzip 压缩数据)。
    • 读取数据后进行处理,再输出处理后的结果。

示例:使用可读流和可写流复制文件

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)适合处理大文件的核心原因是其分块处理数据的特性,无需将整个文件加载到内存中,而是逐块读取、处理和写入,显著降低内存占用并提高效率。

流适合处理大文件的原因

  1. 低内存占用:大文件(如 10GB)无法一次性加载到内存,流通过每次读取一小块数据(默认 64KB),内存占用保持稳定。
  2. 高效处理:数据可边读取边处理(如解析、过滤),无需等待整个文件加载完成,减少处理延迟。
  3. 管道操作:支持 pipe() 方法将多个流串联(如读取 → 压缩 → 写入),简化大文件处理流程。

流的典型使用场景

1.** 大文件复制 **:

const fs = require('fs');
// 使用 pipe 快速复制大文件
fs.createReadStream('large-file.iso').pipe(fs.createWriteStream('copy-large-file.iso'));

2.** 日志实时处理 **:
监听日志文件的新增内容并实时分析(如监控系统)。

const fs = require('fs');
const readStream = fs.createReadStream('app.log', { tail: true }); // 模拟尾行监听
readStream.on('data', (chunk) => {
  console.log('新日志:', chunk.toString());
  // 实时分析逻辑(如错误检测)
});

3.** 数据压缩/解压 **:
结合 zlib 模块对流数据进行压缩(无需加载整个文件到内存)。

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 服务器中通过流发送大文件(如视频、下载资源)。

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 格式。

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 字符序列(文本数据)
编码方式 可通过编码(如 utf8base64)转换为字符串 本质是 Unicode 编码,可通过 Buffer.from() 转为 Buffer
长度计算 length 属性返回字节数 length 属性返回字符数(可能与字节数不同)
不可变性 长度固定,内容可修改(内存分配后大小不变) 不可变,修改会创建新字符串
适用场景 二进制数据(文件、网络、加密) 文本数据(用户输入、显示内容)

示例:Buffer 与字符串的转换

// 字符串转 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。
// 创建 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 的整数)。

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。

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)。

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。

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:当前大)。

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

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();

自定义回调函数的转换

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),可直接使用,无需手动转换。

二、120道Node.js面试题目录列表

文章序号 Node.js面试题120道
1 Node.js面试题及详细答案120道(01-15)
2 Node.js面试题及详细答案120道(16-30)
3 Node.js面试题及详细答案120道(31-42)
4 Node.js面试题及详细答案120道(43-55)
5 Node.js面试题及详细答案120道(56-68)
6 Node.js面试题及详细答案120道(69-80)
7 Node.js面试题及详细答案120道(81-92)
8 Node.js面试题及详细答案120道(93-100)
9 Node.js面试题及详细答案120道(101-110)
10 Node.js面试题及详细答案120道(111-120)

网站公告

今日签到

点亮在社区的每一天
去签到