# Node

# 1.node引入模块的过程是什么?(来自深入浅出nodejs)

路径分析(分析是node的核心模块,如http,还是文件路径或者是第三方依赖) ->文件定位(后缀优先级js>json>node) ->编译执行

  • node会对引入过的模块进行缓存,缓存的是编译执行后的对象。从缓存加载不需要这三步。
  • node模块分为两类:node提供的核心模块,用户编写的文件模块。
  • 核心模块加载速度比文件模块快(因为有部分模块在node进程启动时就被直接加载进内存,这部分文件不需要再进行文件定位和编译执行)。

# 2.nodejs有哪些全局对象?

global,process,console,module,export

# 3.process常用方法?

process.env process.argv process.exit

# 4.nodejs的事件循环?

(执行中是IO事件,setImmediate在当前队列中立即执行,setTimeout/setInterval把执行定时到下一个队列 process.nextTick在当前队列执行完,下次遍历前执行)

  • 顺序:IO事件->setImmediate->setTimeout/setInterval->process.nextTick
  • 总共是6个阶段,
  • 从poll开始,这个阶段会检查是否有io事件,执行io相关回调
  • 到check阶段,这个阶段会执行setImmediate的回调
  • 到close callback 这个阶段执行关闭的回调函数
  • 到timer 这个阶段会执行setTimeout和setInterval的回调
  • 到pending callback这个阶段执行之前延迟的回调
  • 到idle,prepare阶段,这个是node系统内部使用的
  • 回到poll开始新的循环。 (微任务会在每个阶段执行完立即执行,nextTick队列,会优先于其他微任务执行)

# 5.有使用过buffer吗?

buffer主要处理二进制数据,在文件上传有用到。=》支持for...of buffer实例类似整型数组,但buffer的大小是固定的无法改变(V8堆外分配内存),全局变量不需要require

# 常用api

  • Buffer.concat(list,[length])返回一个合并了list中所有buffer的新buffer
  • Buffer.from(array)使用数组创建buffer
  • Buffer.alloc(size)创建一个buffer空间
  • Buffer.isBuffer(obj)判断是否是buffer

# 说说typedArray和unit8Array

引入typedArray之前,js没有读取获操作二进制数据的机制,typeArray用来实现Uint8Array=》8位无符号整型数组,typedArray是统称

# 6.nodejs同步和异步怎么理解?

  • 同步会阻塞IO,所以一般只在项目启动时使用,用来加载配置文件,初始化各种中间件。
  • 异步是通过一次次循环事件队列来实现

# 7.如何防止程序奔溃?

通过try catch捕获异常(可以设置一个全局异常处理器,捕获程序抛出的异常,捕获到把异常打印到控制台)

# 8.怎么调试nodejs?

node-- debug 文件名

# 9.node网络模块有哪些?

常用HTTP、NET

# 10.npm作用?

是包管理器,可以管理项目的依赖及依赖版本号

# 11.nodejs导入模块和导入js文件有什么区别?

导入模块按名称导入,导入文件按路径导入

# 12.有使用过stream流吗?

主要用在读写文件。=》流可读,可写,或者可读可写 =》fs.createReadStream('a.txt') fs.createWriteStream('a.txt')

# buffer和stream之间如何转换?

=》stream to buffer 通过stream.on(data, data=>buffer.push(data))把每个buffer插入buffers数组,然后再通过buffer.concat把这些buffer组合到一起 =》buffer to stream 可以通过duplex(可读可写流)把缓存push到stream中 let stream = new Duplex() stream.push(buffer)

# buffer和stream区别?

buffer是取完数据一次性操作,stream是边取边操作

# 13.fs模块常用?

  • 文件流 fs.createReadStream('a.txt') fs.createWriteStream('a.txt')
  • 同步文件读写 fs.readFileSync() fs.writeFileSync()
  • 异步文件读写 fs.readFile() fs.writeFile()

# 14.nodejs优缺点?

  • 非阻塞IO,适合IO密集型的场景(CPU密集型给node主要挑战是:如果有长时间的计算将导致CPU时间片不能释放,使后续IO无法发起。IO阻塞的性能浪费远比CPU小)
  • 单进程,单线程。不用担心锁和线程同步的问题(js部分是单线程,但是底层IO操作用到了libuv,这块不是单线程) (缺点:一旦一个地方崩溃,整个系统都崩溃) *node跨平台是通过libuv库,事件循环也是这个库实现的

# 15.nodejs模块加载和使用规则是什么?=》cjs

  • 遵循commonjs规范,使用require加载文件 使用exports/module.export导出文件 =》cjs不能直接在浏览器使用 (amd代表库是require.js,早期commonjs代表库是sea.js,由淘宝玉伯开发) (现在统一为nodejs遵循commonjs规范,浏览器遵循esm规范(es6才有的,早期用amd))

# exports和module.export区别

本质无区别,最终暴露的都是module.export。但不能直接对exports赋值,没有任何效果

# 16.常见npm命令?

npm install | npm uninstall | npm init | npm config set

# 17.什么是前后端分离?

前端通过ajax调用后端提供的接口进行数据交互

# 18.v8垃圾回收机制(v8限制64位机器内存最大约1.4G,32机器0.7G。node v14版本的内存为2G,V8自身是用C++)=》完整

(64位新生代空间64M,老生代1400MB 32位新生代空间32M,老生代700M) V8采用分代式垃圾回收=》对象生命周期长短不一,不同回收机制能有更好的效果

  • 分代式垃圾回收机制,将内存区分成老年代和新生代。(新生代内存占用小可以用复制的方式,老生代内存占用大不能用)
  • 新生代中存放的是生存时间短的对象,老生代中存放的是生存时间长的对象。
  • 新生代回收使用scavenge算法(牺牲空间换时间,具体实现采用了cheney算法):通过将存活对象在两个semiSpace空间中复制来实现垃圾回收。
  • cheney算法:它将堆内存一分为二,每个部分称为semiSpace,只有一个在使用中,使用中的是from,空闲的是to空间。 分配对象会在from空间进行分配,回收会检查from存活的对象,非存活对象会被释放,存活对象会复制到to空间,然后清空from空间,最后from,to交换 =》拷贝和交换from,to空间主要是为了让内存空间保持连续
  • scavenge算法缺点是只能使用堆内存的一半,由划分空间和复制机制决定的。但因为它只复制存活的对象,而对于生命周期短的场景来说存活对象只占少部分,所以由时间效率上的优势。 (scavenge算法有很大的空间开销。所以老生代不适合,会浪费大量内存)

# 一开始都在新生代中,达到对象晋升条件后晋升为老生代

  1. 对象经历过一次Scavenge回收
  2. To空间已经使用超过25%(To空间对象会很快移动到老年代,防止新生代的空间被耗尽)

# 为什么是25%?

当这次scavenge回收完成后,会发生角色交换,如果占比过高,会影响后续的内存分配

# 老年代使用什么算法?

老年代回收使用标记清除+标记整理算法 第一次扫描标记存活的对象,第二次扫描清除未被标记的对象 将存活对象往内存的一端移动(整理操作,保证存活对象内存空间连续),清除掉存活对象外的内存

# 老生代为什么还要用标记整理?

因为进行一次清除后,内存空间会出现不连续的情况(会对后续内存分配造成影响)。所以需要整理碎片空间(使内存空间变得连续)

# 全停顿问题(全停顿影响性能)

垃圾回收时需要将应用逻辑先暂停下来,等执行完垃圾回收再继续执行应用逻辑,这就是全停顿,对新生代影响较小,但对老生代影响大。 老生代引入增量标记,将标记阶段分成若干个步骤,每运行一段时间就让程序执行一会,再进行标记(主线程停顿进行垃圾回收然后继续执行主线程然后再垃圾回收)

# 三色标记法(黑白灰):

黑色表示被GC ROOT引用,而且子节点已经标记完成 灰色表示被GC ROOT引用,但是子节点还没被处理 白色表示这个节点没有被访问到,如果本轮垃圾回收结束,节点还是白色,说明是垃圾数据,对应内存会被回收 增量回收是并发进行的,增量回收条件: 不能让黑色节点指向白色节点。 通常使用写屏障(Write-barrier)机制来实现这个约束条件: 当发生了黑色节点引用了白色节点的情况,写屏障会强制将被引用的白色节点变成灰色,这种方法也被称为强三色不变性

# 对象如何释放?

可达性算法。将GC ROOT对象作为起始点,向下搜索,搜索经过的路径称为引用链。当对象到起始点没有任何引用链就是不可用。 即使不可达的对象,也不是立即释放,会先进行标记,然后进行筛选,会被放进一个队列中依次进行回收 如果又有对象引用,就不会被回收。如果一个新生代对象经过多次复制后还存活,会晋升到老年代 对象From空间复制到To空间,如果To空间超过25%,也会直接晋升到老年代 (新生代用全停顿问题不大,因为新生代垃圾回收速度快)

# 内存分配机制?

  1. 分配你需要的内存
  2. 使用分配到的内存读/写
  3. 不需要的时候释放
  4. (C语言可以通过代码手动分配和手动释放,即手动垃圾回收。这两个java和js都默认处理,即自动垃圾回收)

# 19.express和koa区别?

  • express内置了许多中间件。koa没有
  • express包含路由,视图渲染等特性,koa只有http模块
  • express主要通过回调实现异步函数,koa主要通过async,await的方式来处理异步

# 20.有哪些常用模块?

http、fs、path、os、url(用于解析url)

# 21.node是单线程单进程如何高效利用多核CPU?

node提供了child_process,通过启动多进程的方式来高效利用

# 22.什么是IPC?

IPC指InterProcess Communication,即进程间通信

# 23.//TODO

# 24.//TODO

# 25.// TODO

# 26.nodejs和js区别是什么?

  • nodejs是运行环境,js是语言(执行js可以在浏览器也可以在nodejs上。因为都内置了js引擎)
  • js在浏览器有dom,bom等浏览器相关对象,nodejs有文件系统,网络系统等

# 27.npm i与npm ci的区别是什么?

  • npm ci会删除node_modules文件夹
  • npm ci要求项目中必须有package-lock.json否则不起作用,npm i不要求

# 28.node中循环引用会发生什么?

在cjs中,遇到require时,会执行require模块中的代码,并缓存执行结果。下次再加载就会直接去取缓存结果,所以不会出现无限循环引用的情况

# 29.v8如何执行js代码?

parser生成抽象语法树=>Ignition生成字节码(字节码不能直接在处理器上运行,需要解释器转成机器码)=>编译器TuiboFan来编译成机器码

# 怎么知道v8版本号?

console.log(process.versions)

# 30.node中如何读取可读流的内容?

// 通过stream流
let data = "";
stream.on("data", (chunk) => (data += chunk));
stream.on("end", () => console.log(data));
1
2
3
4

# 31.如何读取大文件内容?

import { createReadStream } from "fs";
const stream = createReadStream("bigfile.json");
1
2

# 32.如何查看文件状态/文件最后修改时间?

node stats

# 33.说说对BFF?

  • BFF层可以对前端的数据进行处理再给后端
  • 优点:后端改动,只需改动BFF层,无需去改前端代码

# 34.koa-router实现原理?

正则匹配=》采用的是path-to-regexp库

# 35.node如何调用c++?

通过napi

# 36.什么是中间件-举例?

比如路由中间件,全局异常捕获中间件,权限中间件

# 37.fs-extra有使用过吗?

是fs的扩展,继承了fs的所有方法,扩展了更多的功能,给fs的方法添加了promise的支持

# 38.如何监控文件的变动?

node有提供一个fs.watch

# 39.node --max-old-space-size=4096什么意思?

最大内存4G