node面试题汇总

131 阅读5分钟

详细介绍一下Node.js中的事件循环机制

与浏览器事件循环的区别

  • 阶段划分不同:虽然浏览器和 Node.js 都有事件循环,但浏览器的事件循环没有像 Node.js 这样明确划分成多个阶段,其主要分为宏任务队列和微任务队列。
  • 宏任务种类不同:浏览器中的宏任务除了定时器、I/O 操作外,还包括用户交互事件、页面渲染等,而 Node.js 中的宏任务主要是与服务器端相关的操作,如文件 I/O、网络请求等。
  • 微任务实现不同:在浏览器中,MutationObserver也属于微任务,而在 Node.js 中没有这个。Node.js 中有process.nextTick(),这在浏览器中是没有的。

过程

  • timers(定时器)阶段
  • I/O callbacks(I/O 回调)阶段
  • idle, prepare(闲置、准备)阶段
  • poll(轮询)阶段

事件循环会不断地轮询操作系统,询问是否有新的 I/O 事件发生。如果有,就从 I/O 队列中取出事件并执行对应的回调函数。当没有新的 I/O 事件时,根据是否有setImmediate()回调等条件来决定是否继续等待或进入下一个阶段。

  • check(检查)阶段
  • close callbacks(关闭回调)阶段

常用模块

HTTP 模块--文件模块--PATH 模块

模块的加载机制和缓存策略

Node.js 采用 CommonJS 规范来处理模块,其模块加载遵循一定的流程和规则,主要分为以下几种情况:

  • 核心模块是 Node.js 自带的模块,如 fshttppath 等。这些模块在 Node.js 启动时就被加载到内存中,加载速度非常快。
  • 文件模块加载
  • 绝对路径:如果使用绝对路径引入模块,Node.js 会直接根据该路径查找并加载模块文件。 当使用相对路径引入模块时,Node.js 会根据当前执行文件的路径,结合相对路径找到对应的模块文件

  • 相对路径:当使用相对路径引入模块时,Node.js 会根据当前执行文件的路径,结合相对路径找到对应的模块文件

  • 扩展名处理:如果在 require 时省略了扩展名,Node.js 会按照 .js.json.node 的顺序依次尝试查找对应的文件。

  • 第三方模块加载

加载流程

  1. Node.js 会从当前执行文件所在目录开始,逐级向上查找 node_modules 目录。
  2. 在找到的 node_modules 目录中,查找与模块名匹配的文件夹。
  3. 如果找到了对应的模块文件夹,会根据该模块的 package.json 文件中的 main 字段指定的入口文件进行加载

JavaScript 运行时环境差异

jsnode
全局对象是 windowglobal,挂在当前模块上
DOM和BOM文件系统操作、网络通信、进程管理
ES和CND和AMD用 CommonJS 规范,通过 require

事件执行机制区别

浏览器环境:事件循环主要负责处理用户交互事件(如点击、滚动)、定时器(如 setTimeoutsetInterval)和异步操作(如 fetch)等。它的任务队列分为宏任务队列和微任务队列,微任务会在当前宏任务执行结束后立即执行

Node.js 环境:事件循环更为复杂,分为多个阶段,包括 timersI/O callbacksidle, preparepollcheckclose callbacks。每个阶段有不同的任务队列,定时器回调在 timers 阶段执行,setImmediate 回调在 check 阶段执行,process.nextTick 属于微任务,会在每个阶段切换前执行。

koa

请求先从外层中间件依次向内层传递,到达最内层中间件处理完核心业务逻辑后,再从内层向外层返回响应,整个过程呈现出一种先入后出的特点 采用pm2进行管理

其他

哪些会导致内存泄露

  • 被视为全局变量,随着每次调用函数,数组不断增大,造成内存泄漏。
  • 未清理的定时器和回调
  • 这些闭包都引用了 largeArray,导致 largeArray 无法被垃圾回收,随着闭包数量的增加,内存占用会不断上升
  • 缓存
  • 这些闭包都引用了 largeArray,导致 largeArray 无法被垃圾回收,随着闭包数量的增加,内存占用会不断上升

如何在实际项目中选择使用 Object.prototype 还是 ES6 Proxy?

拦截范围
  • Object.defineProperty() :只能劫持对象的单个属性,对于对象新增的属性无法自动劫持,需要手动调用 Vue.set() 方法来实现响应式。对于数组,它只能劫持部分数组方法,如 pushpop 等变异方法,对于通过索引修改数组元素的操作无法劫持。
  • ES6 Proxy:可以对整个对象进行代理,能够拦截对象的所有操作,包括属性的读取、设置、删除、枚举等。对于数组,Proxy 可以完全劫持数组的各种操作,不需要特殊处理。
性能
  • Object.defineProperty() :在处理嵌套对象时,需要递归地对每个属性进行劫持,性能开销较大。尤其是在对象属性较多或嵌套层级较深时,初始化响应式系统的时间会明显增加。
  • ES6 ProxyProxy 是对整个对象进行代理,不需要递归处理,性能相对较好。特别是在处理大型对象时,Proxy 的优势更加明显。
代码复杂度
  • Object.defineProperty() :由于需要手动处理属性的劫持和更新,代码逻辑相对复杂。特别是在处理嵌套对象和数组时,需要编写大量的辅助函数来实现响应式。
  • ES6 ProxyProxy 的代码更加简洁,只需要定义一个代理对象和处理程序,就可以实现对对象的全面劫持。

react面试题

  • 对于简单的组件内部状态,使用 useState 或 useReducer 即可。
  • 当需要在多层级组件之间共享少量状态时,React Context 是一个不错的选择。
  • 对于大型复杂应用,涉及大量状态管理和复杂的状态逻辑,推荐使用 Redux 或 MobX。

ts面试题