nodejs 的深入理解与常见面试题汇总

nodejs 的深入理解与常见面试题汇总

  1. require机制

    • 什么是CommonJS

      CommonJS CMJ 社区标准

      ES Module ESM 官方标准

      每个文件就是一个模块,拥有自己独立的作用域、变量、方法等,对其他模块都不可见。CommonJS规范规定:每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。

    • 模块分类

      • 原生(核心)模块:Node提供的模块我们都称之为原生模块
        • 内建模块:Node.js 原生提供的模块终,由纯c/c++编写的称为内建模块
        • 全局模块:Node.js在启动时,会生成一个全局变量process
        • 除了上面两种可以直接require的所有原生模块
      • 文件模块:用户编写的模块
        • 普通文件模块:node_modules下的模块,或者我们自己开发时候写的文件
        • c++扩展模块:用户自己编写的c++扩展模块或者第三方的c++扩展模块
    • require函数的本质

      • 根据模块路径 得到一个模块的ID(相对路径->绝对路径)

      • 用模块的ID判断是否存在缓存

      • 真正运行模块代码辅助函数(模块代码实际上是在一个函数中执行的 可以隔离变量 避免全局污染

      • 辅助函数中的参数就是

        function _require(exports,require,module,__filename,__dirname)
        
    • require加载规则

      • require优先加载缓存中的模块。同一个模块第一次require之后,就会缓存一份,下一次require时直接从缓存中取
      • 如果是加载核心模块,直接存内存中加载,并缓存。
      • 如果是相对路径,则根据路径加载自定义模块,并缓存
      • 如果不是自定义模块,和不是核心模块,则加载第三方模块。
  2. 为什么需要箭头函数。

    • 消除函数的二义性
      • 普通js函数 既可以通过函数调用方式 也可以通过new的方式 这便是函数的二义性。
      • 箭头函数只能通过函数调用的方式来调用函数 不能通过new的方式。
    • 箭头函数和普通函数有什么区别
      • 箭头函数中的this指向外部,内部无this
      • 箭头函数无arguments参数 因为当时没有剩余参数
      • 箭头函数没有prototype
      • 箭头函数不能通过new调用
  3. 作用域链和闭包得关系

    闭包:理念

    作用域链:实现手段

    作用域链是实现闭包的一种手段

  4. Node的事件循环

    • 事件循环是什么

      • JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。
      • 通俗一点讲,可以将事件循环理解为一个一直不停在绕圈跑步的人,在跑步的过程中,每隔一段距离就有可能出现一道关卡拦着这个人,比如独木桥、沙坑、矮墙等障碍,需要这人拿下这些障碍后才能继续前进,跑者需要做的事情就是一直不停的绕这个圈跑步,遇到关卡就执行这个关卡的任务。
      • 跑步过程中不断出现的障碍就是任务
        • 假设这个圈有 100 米,当这个跑者从七点出发在跑完一圈的过程中,可能出现的问题以及他们出现的位置是这样的:
        • 20 米:从任务队列(Task Queue)中取出最早的一个任务(如:JavaScript 事件回调、setTimeout或者 setInterval 的回调函数等)。
        • 40 米:执行该任务。可能的任务包括以下动作:
          • 对 DOM 进行操作(添加、删除、修改元素等)。
          • 发送 HTTP 请求、处理相应。
          • 更新 UI,如绘制动画。
          • 执行其他 JavaScript 代码。
        • 60米:一旦任务完成,处理微任务队列(Microtask Queue)。次队列包括诸如 Promise.then回调、MutationObserver 回调等微任务。浏览器会继续执行微任务队列中的任务,直到该队列为空。
        • 80 米:进行浏览器渲染更新:对于涉及 DOM 修改或者样式变化的操作,浏览器会进行重新布局(重排)和重绘。由于重排和重绘涉及性能开销,浏览器会在适当的时机将这些操作进行合并,以优化性能。
        • 100 米:当这些步骤完成后,事件循环返回到第一步,继续检查任务队列以处理下一个任务。
    • 可以在浏览器的任务管理器中查看当前的所有进程

      • 浏览器进程

        主要负责界面显示、用户交互、子进程管理等。浏览器进程内部会启动多个线程处理不同的任务。

      • 网络进程

        负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务。

      • 渲染进程

        渲染进程启动后,会开启一个渲染主线程,主线程负责执行 HTML、CSS、JS 代码。

        默认情况下,浏览器会为每一个标签页开启一个新的渲染进程,以保证不同的标签页之间不相互影响。

        渲染线程是是浏览器中最烦忙的线程,只要工作如下:

        • 解析 HTML
        • 解析 CSS
        • 计算样式
        • 布局
        • 处理图层
        • 每秒把页面画 60 次
        • 执行全局 JS 代码
        • 执行事件处理函数
        • 执行计时器的回调函数
      • 渲染主线程怎么调度任务?

        排队

        • 在最开始的时候,渲染主线程会进入一个无限循环

        • 每一次循环会检查消息队列中是否有任务存在。如果有,就取出第一个任务执行,执行完一个后进入下一次循环;如果没有,则进入休眠状态。

        • 其他所有线程(包括其他进程的线程)可以随时向消息队列添加任务。新任务会加到消息队列的末尾。在添加新任务时,如果主线程是休眠状态,则会将其唤醒以继续循环拿取任务。

          整个过程,被称之为事件循环(消息循环)

      • 任务有优先级吗?

        过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式。

        任务没有优先级,在消息队列中先进先出

        消息队列是有优先级的

        根据 W3C 的最新解释:

        • 每个任务都有一个任务类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属于不同的队列。在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。

        • 浏览器必须准备好一个微队列,微队列中的任务优先所有其他任务执行。

          随着浏览器的复杂度急剧提升,W3C 不再使用宏队列的说法

        • 在目前 chrome 的实现中,至少包含了下面的队列:

          • 延时队列:用于存放计时器到达后的回调任务,优先级【中】

          • 交互队列:用于存放用户操作后产生的事件处理任务,优先级【高】

          • 微队列:用户存放需要最快执行的任务,优先级【最高】


            添加微队列的主要方式是使用 Promise、MutationObserver。

      • 面试题:JS 中的计时器能做到精确计时吗?为什么?

        参考答案:

        不行,因为:

        1. 计算机硬件没有原子钟,无法做到精确计时
        2. 操作系统的计时函数本身就有少量偏差,由于 JS 的计时器最终调用的是操作系统的函数,也就携带了这些偏差
        3. 按照 W3C 的标准,浏览器实现计时器时,如果嵌套层级超过 5 层,则会带有 4 毫秒的最少时间,这样在计时时间少于 4 毫秒时又带来了偏差
        4. 受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此又带来了偏差/
    • 事件循环流程

      • 在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环的过程我们称为Tick。

      • 每个Tick的过程就是查看是否有事件待处理。如果有就取出事件以及相关的回调函数,然后进入下一个循环,如果不再有事件处理,就退出进程。

    • 在每个tick的过程中,如何判断是否有事件需要处理呢?

      • 每个事件循环中有一个或者多个观察者,而判断是否有事件需要处理的过程就是向这些观察者询问是否有需要处理的事件。
      • 再Node中,事件主要来源于网络请求、文件的i/o等,这些事件对应的观察者有文件i/o观察者、网络i/o观察者。
      • 事件循环是一个典型的生产者/消费者模型。异步i/o,网络请求等则是事件的生产者。源源不断的为Node提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。
      • 在Windows下,这个循环基于IOCP创建,在*nix下则基于多线程创建。
  5. V8的垃圾回收机制

    • 如何查看V8的内存使用情况

      使用process.memoryUsage(),返回如下。

      {
        rss: 4935680,
        heapTotal: 1826816,
        heapUsed: 650472,
        external: 49879
      }
      
    • V8的内存限制是多少,为什么这样设计

      • 64位系统下是1.4GB,32位系统下是0.7GB。
      • 因为1.5GB的垃圾回收堆内存,V8需要花费50毫秒以上,做一次非增量式的垃圾回收甚至需要1秒以上。这是垃圾回收中引起JavaScript线程暂停执行的事件,再这样的花销下,应用的性能和影响力都会直线下降
  6. Buffer模块

    • 新建Buffer会占用V8分配的内存吗
      不会 Buffer属于堆外内存,不是V8分配的。
  • 36
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值