Node.js面试题和答案整理(求职面试必备)

前言
“Any application that can be written in JavaScript, will eventually be written in JavaScript.” -Jeff Atwood            
这句话早在 2007 年就已经说过,我们可以说直到现在都证明是正确的。你可以想到任何技术关键字,并且可能会围绕它构建一个 JavaScript 库。因此,如果它如此受欢迎且需求旺盛,那么这可能是一种非常值得学习的编程语言。但这并不是唯一需要的技能,因为你必须应用它来解决实际问题。此类问题之一是构建可扩展的产品。
Gen Z 后端

在 jQuery 动画开发转向单页应用程序以更好地控制 ui/ux 之后,出现了前端框架,例如 angular js 和 angular。在那之后,JavaScript 可以移植到几乎任何作为独立应用程序存在和运行的现代机器上,即 Node.js。它被广泛接受为后端框架,并在 2020 年的 StackOverflow 调查中连续第二年位居榜首。

由于开发人员正忙于获得 node.js 的经验,因此最好有一个精心挑选的Node.js面试题和答案合集来修改。此外,为了进一步巩固你对 Javascript 的了解,请参阅此来源。
初学者 Node.js 面试问题1. Javascript 中的第一类函数是什么?
Node.js面试题解析:当函数可以像任何其他变量一样被对待时,这些函数就是一等函数。还有很多其他的编程语言,例如Scala、Haskell等,包括JS。现在因为这个函数可以作为参数传递给另一个函数(回调)或者一个函数可以返回另一个函数(高阶函数)。map() 和 filter() 是常用的高阶函数。
2. Node.js常见面试题有哪些:什么是 Node.js,它是如何工作的?
Node.js 是一个使用 JavaScript 作为其脚本语言并运行 Chrome 的 V8 JavaScript 引擎的虚拟机。基本上,Node.js 基于事件驱动的架构,其中 I/O 异步运行,使其轻量级和高效。它也被用于开发桌面应用程序以及一个名为电子的流行框架,因为它提供 API 来访问操作系统级功能,如文件系统、网络等。
3、你的node.js项目中是如何管理包的?
它可以由许多软件包安装程序及其相应的配置文件进行管理。其中大部分使用 npm 或 yarn。两者都提供几乎所有具有控制特定环境配置的扩展功能的 javascript 库。为了维护项目中安装的库版本,我们使用 package.json 和 package-lock.json 以便将该应用程序移植到不同的环境没有问题。
4. Node.js 如何比其他最常用的框架更好?

  • Node.js 提供了开发的简单性,因为它的非阻塞 I/O 和基于偶数的模型导致较短的响应时间和并发处理,这与开发人员必须使用线程管理的其他框架不同。 
     
  • 它运行在用 C++ 编写的 chrome v8 引擎上,并且在不断改进中具有高性能。 
     
  • 此外,由于我们将在前端和后端都使用 Javascript,因此开发速度会快得多。 
     
  • 最后,有充足的库,因此我们不需要重新发明轮子。
5. 解释“控制流”如何控制函数调用的步骤?
  • 控制执行顺序
  • 收集数据
  • 限制并发
  • 在程序中调用以下步骤。
6. Node.js 有哪些常用的计时特性?
  • setTimeout/clearTimeout  – 用于在代码执行中实现延迟。
  • setInterval/clearInterval  – 用于多次运行代码块。
  • setImmediate/clearImmediate  – 用于在事件循环周期结束时设置代码的执行。
  • process.nextTick  – 用于在下一个事件循环周期开始时设置代码的执行。
7、使用promise代替回调有什么好处?
使用 promise 的主要优点是你可以得到一个对象来决定异步任务完成后需要采取的操作。这提供了更易于管理的代码并避免了回调地狱。
8.NodeJS中的fork是什么?
叉子通常用于生成子进程。在 node 中,它用于创建一个新的 v8 引擎实例来运行多个 worker 来执行代码。
9. Node.js面试题和答案合集:为什么 Node.js 是单线程的?
Node.js 是作为异步处理实验明确创建的。这是为了尝试在单个线程上进行异步处理的新理论,而不是通过不同框架的现有基于线程的缩放实现。
10. 如何在 Node.js 中创建一个返回 Hello World 的简单服务器?
var http = require("http"); http.createServer(function (request, response) { response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello World\n'); }).listen(3000);

11. Node.js 有多少种 API 函数?
有两种类型的 API 函数:
  • 异步、非阻塞函数——主要是可以从主循环中分叉出来的 I/O 操作。
     
  • 同步、阻塞函数——主要是影响在主循环中运行的进程的操作。
12.什么是REPL?
PL中的Node.js代表- [R  EAD,ê缬氨酸,P  RINT,和大号接力,其进一步的手段在旅途中评估代码。
13. 列出 async.queue 作为输入的两个参数?
  • 任务功能
  • 并发值
14.module.exports的目的是什么?
这用于公开要在项目其他地方使用的特定模块或文件的功能。这可用于将所有类似功能封装在一个文件中,从而进一步改进项目结构。

例如,你有一个包含所有 utils 函数的文件,其中包含 util 以获取问题陈述的不同编程语言的解决方案。
const getSolutionInJavaScript = async ({ problem_id }) => { ... }; const getSolutionInPython = async ({ problem_id }) => { ... }; module.exports = { getSolutionInJavaScript, getSolutionInPython }

因此使用 module.exports 我们可以在其他文件中使用这些函数:
const { getSolutionInJavaScript, getSolutionInPython} = require("./utils")

15. 可以使用哪些工具来确保一致的代码风格?
ESLint 可以与任何 IDE 一起使用,以确保一致的编码风格,这进一步有助于维护代码库。 
中级Node.js面试题和答案合集16.你怎么理解回调地狱?
async_A(function(){ async_B(function(){ async_C(function(){ async_D(function(){ .... }); }); }); });

对于上面的例子,我们传递了回调函数,这使得代码不可读且不可维护,因此我们应该更改异步逻辑以避免这种情况。
17. Node JS 中的事件循环是什么?
Node.js面试题解析:无论是异步的是什么,都由使用队列和侦听器的事件循环管理。我们可以使用下图来了解这个想法:
Node.js面试题和答案整理(求职面试必备)

文章图片
Node.js 事件循环
因此,当需要执行(或 I/O)异步函数时,主线程将其发送到不同的线程,从而允许 v8 继续执行主代码。事件循环涉及具有特定任务的不同阶段,例如定时器、挂起的回调、空闲或准备、轮询、检查、具有不同 FIFO 队列的关闭回调。同样在迭代之间,它会检查异步 I/O 或计时器,如果没有则干净地关闭。
18. 如果 Node.js 是单线程的,那么它如何处理并发?
主循环是单线程的,所有异步调用都由 libuv 库管理。
例如:
const crypto = require("crypto"); const start = Date.now(); function logHashTime() { crypto.pbkdf2("a", "b", 100000, 512, "sha512", () => { console.log("Hash: ", Date.now() - start); }); } logHashTime(); logHashTime(); logHashTime(); logHashTime();

这给出了输出:
Hash: 1213 Hash: 1225 Hash: 1212 Hash: 1222

这是因为 libuv 设置了一个线程池来处理这种并发。线程池中有多少线程取决于内核数,但你可以覆盖它。
19. process.nextTick() 和 setImmediate() 的区别?
两者都可用于通过侦听器函数切换到异步操作模式。 

process.nextTick() 将回调设置为执行,但 setImmediate 将回调推送到要执行的队列中。所以事件循环以如下方式运行

timers–> pending callbacks–> idle,prepare–> connections(poll,data,etc)–> check–> close callbacks
在这个过程中,nextTick() 方法将回调函数添加到下一个事件队列的开头和 setImmediate() 方法将函数置于下一个事件队列的检查阶段。
20、Node.js是如何克服I/O操作阻塞的问题的?
由于Node有一个事件循环,可以在不阻塞主函数的情况下以异步方式处理所有的 I/O 操作。 

因此,例如,如果需要发生某些网络调用,它将在事件循环中而不是主线程(单线程)中进行调度。如果有多个这样的 I/O 调用,每一个都会相应地排队单独执行(主线程除外)。 
因此,即使我们有单线程 JS,I/O 操作也是以非阻塞方式处理的。
21.Node.js常见面试题有哪些:如何在node.js中使用async await?
下面是一个使用 async-await 模式的例子:
// this code is to retry with exponential backoff function wait (timeout) { return new Promise((resolve) => { setTimeout(() => { resolve() }, timeout); }); } async function requestWithRetry (url) { const MAX_RETRIES = 10; for (let i = 0; i < = MAX_RETRIES; i++) { try { return await request(url); } catch (err) { const timeout = Math.pow(2, i); console.log('Waiting', timeout, 'ms'); await wait(timeout); console.log('Retrying', err.message, i); } } }

22. 什么是 node.js 流?
Streams 是 EventEmitter 的实例,可用于在 Node.js 中处理流数据。它们可用于处理和操作通过网络传输的大文件(视频、mp3 等)。他们使用缓冲区作为临时存储。

流主要有四种类型:
  • Writable:可以写入数据的流(例如,fs.createWriteStream())。
  • 可读:可以从中读取数据的流(例如,fs.createReadStream())。
  • 双工:既可读又可写的流(例如,net.Socket)。
  • 转换:可以在写入和读取数据时修改或转换数据的双工流(例如,zlib.createDeflate())。
23. 什么是 node.js 缓冲区?
一般来说,缓冲区是一种临时内存,主要被流用来保存一些数据直到被消耗。缓冲区引入了除 JavaScript 的 Unit8Array 之外的其他用例,主要用于表示固定长度的字节序列。这也支持传统编码,如 ASCII、utf-8 等。它是 v8 之外的固定(不可调整大小)分配的内存。
24.什么是中间件?
中间件介于你的请求和业务逻辑之间。它主要用于捕获日志并启用速率限制、路由、身份验证,基本上任何不属于业务逻辑的部分。还有第三方中间件,例如 body-parser,你可以为特定用例编写自己的中间件。
25. 解释一下 Node.js 中的 Reactor 模式是什么?
Reactor 模式再次成为非阻塞 I/O 操作的模式。但总的来说,这用于任何事件驱动的架构。 

这里面有两个组件: 1. Reactor 2. Handler。

Reactor:它的工作是将 I/O 事件分派给适当的处理程序
Handler:它的工作是实际处理这些事件
26. 为什么要分离 Express 应用程序和服务器?
服务器负责初始化路由、中间件和其他应用程序逻辑,而应用程序具有将由服务器启动的路由服务的所有业务逻辑。这确保了业务逻辑与应用程序逻辑的封装和解耦,从而使项目更具可读性和可维护性。
27.对于Node.js,谷歌为什么使用V8引擎?
那么,还有其他选择吗?是的,当然,我们有来自 Firefox 的Spidermonkey,来自 Edge 的 Chakra,但谷歌的 v8 是最先进的(因为它是开源的,所以有一个巨大的社区帮助开发功能和修复错误)和最快的(因为它是用 C++ 编写的)我们直到现在作为 JavaScript 和 WebAssembly 引擎。它可以移植到几乎所有已知的机器上。
28.描述Node.js的退出代码?
退出代码让我们了解进程如何终止/终止背后的原因。 

其中一些是:
  • 未捕获的致命异常 - (code - 1) - 出现了未处理的异常
  • 未使用 - (code - 2) - 这是 bash 保留的
  • 致命错误 - (代码 - 5) - V8 中出现错误,描述的 stderr 输出
  • 内部异常处理程序运行时失败 - (代码 - 7) - 调用引导函数时出现异常
  • 内部 JavaScript 评估失败 -(代码 - 4)- 引导过程在评估时未能返回函数值时出现异常。
29.解释Node.js中存根的概念?
【Node.js面试题和答案整理(求职面试必备)】存根用于编写测试,这是开发的重要组成部分。它取代了正在测试的整个功能。  

这有助于我们需要测试的场景:
  • 使测试缓慢且难以编写的外部调用(例如 HTTP 调用/数据库调用)
  • 为一段代码触发不同的结果(例如,如果抛出错误/如果通过会发生什么)
例如,这是函数:
const request = require('request'); const getPhotosByAlbumId = (id) => { const requestUrl = `https://jsonplaceholder.typicode.com/albums/${id}/photos?_limit=3`; return new Promise((resolve, reject) => { request.get(requestUrl, (err, res, body) => { if (err) { return reject(err); } resolve(JSON.parse(body)); }); }); }; module.exports = getPhotosByAlbumId; To test this function this is the stub const expect = require('chai').expect; const request = require('request'); const sinon = require('sinon'); const getPhotosByAlbumId = require('./index'); describe('with Stub: getPhotosByAlbumId', () => { before(() => { sinon.stub(request, 'get') .yields(null, null, JSON.stringify([ { "albumId": 1, "id": 1, "title": "A real photo 1", "url": "https://via.placeholder.com/600/92c952", "thumbnailUrl": "https://via.placeholder.com/150/92c952" }, { "albumId": 1, "id": 2, "title": "A real photo 2", "url": "https://via.placeholder.com/600/771796", "thumbnailUrl": "https://via.placeholder.com/150/771796" }, { "albumId": 1, "id": 3, "title": "A real photo 3", "url": "https://via.placeholder.com/600/24f355", "thumbnailUrl": "https://via.placeholder.com/150/24f355" } ])); }); after(() => { request.get.restore(); }); it('should getPhotosByAlbumId', (done) => { getPhotosByAlbumId(1).then((photos) => { expect(photos.length).to.equal(3); photos.forEach(photo => { expect(photo).to.have.property('id'); expect(photo).to.have.property('title'); expect(photo).to.have.property('url'); }); done(); }); }); });

高级Node.js面试题和答案合集30. Node.js 中的事件发射器是什么?
EventEmitter 是一个 Node.js 类,它包含了基本上能够发出事件的所有对象。这可以通过使用 eventEmitter.on() 函数附加由对象发出的命名事件来完成。因此,每当此对象抛出一个偶数时,都会同步调用附加的函数。
const EventEmitter = require('events'); class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.on('event', () => { console.log('an event occurred!'); }); myEmitter.emit('event');

31. 通过集群提高 Node.js 性能。
Node.js面试题解析:Node.js 应用程序在单个处理器上运行,这意味着默认情况下它们不利用多核系统。集群模式用于启动多个 node.js 进程,从而拥有多个事件循环实例。当我们在后台的 nodejs 应用程序中开始使用集群时,会创建多个 node.js 进程,但还有一个称为集群管理器的父进程,负责监控我们应用程序的各个实例的健康状况。
Node.js面试题和答案整理(求职面试必备)

文章图片
Node.js 中的集群
32. Node.js常见面试题有哪些:什么是线程池以及在 Node.js 中哪个库处理它
线程池由 libuv 库处理。libuv 是一个多平台的 C 库,它支持基于异步 I/O 的操作,例如文件系统、网络和并发。 
Node.js面试题和答案整理(求职面试必备)

文章图片
线程池
33. 什么是 WASI,为什么要引入它?
Web 程序集通过使用 WASI 类实现的 node.js 中的 WASI API提供了WebAssembly 系统接口规范的实现。WASI 的引入是通过记住它可以通过一系列类似于 POSIX 的函数来使用底层操作系统,从而进一步使应用程序能够更有效地使用资源和需要系统级访问的功能。
34. 工作线程与集群有何不同?
集群:
  • 每个 CPU 上有一个进程与 IPC 进行通信。
  • 如果我们想让多个服务器通过单个端口接受 HTTP 请求,集群会很有帮助。
  • 进程在每个 CPU 中产生,因此将具有单独的内存和节点实例,这将进一步导致内存问题。
工作线程:
  • 总共只有一个进程有多个线程。
  • 每个线程都有一个 Node 实例(一个事件循环,一个 JS 引擎),大多数 API 都可以访问。
  • 与其他线程共享内存(例如 SharedArrayBuffer)
  • 这可用于处理数据或访问文件系统等 CPU 密集型任务,因为 NodeJS 是单线程的,利用工作线程可以使同步任务更高效。
35.如何测量异步操作的持续时间?
Performance API 为我们提供了确定必要性能指标的工具。一个简单的例子是使用 async_hooks 和 perf_hooks
'use strict'; const async_hooks = require('async_hooks'); const { performance, PerformanceObserver } = require('perf_hooks'); const set = new Set(); const hook = async_hooks.createHook({ init(id, type) { if (type === 'Timeout') { performance.mark(`Timeout-${id}-Init`); set.add(id); } }, destroy(id) { if (set.has(id)) { set.delete(id); performance.mark(`Timeout-${id}-Destroy`); performance.measure(`Timeout-${id}`, `Timeout-${id}-Init`, `Timeout-${id}-Destroy`); } } }); hook.enable(); const obs = new PerformanceObserver((list, observer) => { console.log(list.getEntries()[ 0]); performance.clearMarks(); observer.disconnect(); }); obs.observe({ entryTypes: [ 'measure'], buffered: true }); setTimeout(() => {}, 1000);

这将为我们提供执行回调所需的确切时间。
36.如何衡量异步操作的性能?
Performance API 为我们提供了确定必要性能指标的工具。 

一个简单的例子是:
const { PerformanceObserver, performance } = require('perf_hooks'); const obs = new PerformanceObserver((items) => { console.log(items.getEntries()[ 0].duration); performance.clearMarks(); }); obs.observe({ entryTypes: [ 'measure'] }); performance.measure('Start to Now'); performance.mark('A'); doSomeLongRunningProcess(() => { performance.measure('A to Now', 'A'); performance.mark('B'); performance.measure('A to B', 'A', 'B'); });

    推荐阅读