特别提示: 本文不成文,谨慎阅读。
《前端面试总结点线面》系列是为了收拢杂而乱的前端领域知识,由点及线,再涉及面,目的是为帮助广大前端同学复习巩固或查漏补缺或增删改查,为了更好的建立前端领域知识体系,为了更好的为前端面试做好准备,从而做一个合格、进步的前端开发工程师。
NodeJS
V8 内存管理模型
node 程序运行中,此进程占用的所有内存称为常驻内存:
- 代码区:存放即将执行的代码片段
- 栈:存放局部变量
- 堆:存放对象、闭包上下文
- 堆外内存:由 c++分配,不受 V8 管理,也不会被 V8 回收。(Buffer 数据)
V8 垃圾回收 ♻️
- 新生代:
- 将内存平均分为两块,使用空间叫 FROM,闲置空间叫 TO。
- 将存活对象分配到 TO 空间,然后清除 FROM 空间
- 调换 FROM 和 TO 空间,继续内存分配
- 新生代晋升老生代
- 多次存活的对象会晋升至老生代
- To 空间内存使用率超过 25%
- 老生代:
- 标记清除 Mark-Sweep (会产生碎片)
- 标记整理 Mark-Compact (整理连续内存)
事件循环
高并发: 单线程 非阻塞 异步 IO(主线程事件循环机制以及底层线程池的实现)
Event Loop
事件循环原理
-
node 初始化
- 初始化 node 环境
- 执行输入代码
- 执行 process.nextTick 回调
- 执行 microtasks
-
进入 Event Loop
- timers 阶段
- 检查 timer 队列是否有到期的 timer(setTimeout/setInterval)回调,有的话将到期的回调按 timerId 升序执行。
- IO callback 阶段
- 检查是否有等待(pending)的 IO 回调,有的话执行
- idle,prepare 阶段
- nodejs 内部调用
- poll 阶段
- 检查是否存在尚未完成的回调,如果存在,分两种情况
- 如果队列不为空(包含到期的定时器和 IO 事件),执行可用回调。
- 如果队列为空,检查是否有 setImmediate 回调,
- 如果有,退出 poll 阶段,进入 check 阶段。
- 如果没有,超时之前 node 阻塞在这里,等待新的事件通知。
- 如果不存在尚未完成的回调,直接退出 poll 阶段
- 检查是否存在尚未完成的回调,如果存在,分两种情况
- check 阶段
- 如果有 setImmediate 回调,执行回调
- closing callback
- 如果套接字或处理函数突然关闭(例如 socket.destroy()),则'close' 事件将在这个阶段发出。
在事件循环的每一个子阶段退出之前,都会执行:
- 检查是否有 process.nextTick,有的话执行
- 执行 microtasks
- 退出当前阶段
- timers 阶段
-
检查是否有活跃的 handlers(定时器,IO 事件句柄)
- 如果有,继续下一轮循环
- 没有,就退出事件循环,退出程序
process.nextTick 🆚 setImmediate
process.nextTick 是 Nodejs 的一个定时器,他是在本轮循环执行的,而且是所有异步任务中最快执行的。Node 执行完所有同步任务,就会去执行 process.nextTick 任务队列。
process.nextTick 并不属于 Event Loop 中的某一阶段,而在 Event loop 的每一个阶段结束之前,直接执行 nextTickQueue 中插入的 tick,并且直到整个 Queue 处理完。
setImmediate 是在当前任务队列的尾部添加事件,也就是说,它指定的事件总在下一次 Eventloop 执行。
递归调用的 process.nextTick 会导致 IO 饥饿,推荐 setImmediate。
示例:
进程 Process
-
查看进程
ps -ef
-
当前进程的启动目录
process.cwd()
-
改变工作目录
process.chdir()
-
标准流
process.stdin
process.stdout
process.stderr
-
child_process.fork 与 POSIX 的 fork 有什么区别? Nodejs 的 child_process.fork()在 UNIX 上的实现最终调用了 POSIX 的 fork,但是 POSIX 需要手动管理子进程的资源释放,child_process.fork 不用担心这个问题,nodejs 自动释放,并且可以在 option 中选择父进程死后是否允许子进程存活
spawn
启动一个子进程来执行命令exec
启动一个子进程带回调参数获知子进程的情况,可指定进程运行的超时时间。fork
加强版的 spawn,返回值是 child_process 对象,可以与子进程交互。
-
child.kill 与 child.send
- 一个是基于信号系统, 一个是基于 IPC.
-
父进程或子进程的死亡是否会影响对方? 什么是孤儿进程?
- 子进程死亡不会影响父进程, 不过子进程死亡时(线程组的最后一个线程,通常是“领头”线程死亡时),会向它的父进程发送死亡信号.
- 反之父进程死亡, 一般情况下子进程也会随之死亡,
- 但如果此时子进程处于可运行态、僵死状态等等的话, 子进程将被进程 1(init 进程)收养,从而成为孤儿进程.
- 另外, 子进程死亡的时候(处于“终止状态”),父进程没有及时调用 wait() 或 waitpid() 来返回死亡进程的相关信息,此时子进程还有一个 PCB 残留在进程表中,被称作僵尸进程.
Cluster
Cluster 是 nodejs 常见的利用多核的办法,它是利用 child_process.fork 实现的,所以 cluster 产生的进程之间是通过 IPC 来通信的,并且它也没有拷贝父进程的空间,而是通过加入 cluster.isMaster 来区分父、子进程。
const cluster = require('cluster')
const http = require('http')
const numCPUs = require('os').cpus().length
if (cluster.isMaster) {
// 仅父进程执行
// fork workers
for(i = 0; i<numCPUs;i++) {
cluster.fork()
}
cluster.on('exit', (worker) => {
console.log(`worker ${worker.process.pid} died`)
})
} else {
// 仅子进程执行
// workers can share any TCP connection
http.createServer((req, res) => {
res.writeHead(200)
res.end('hello world')
}).listen(3000)
}
Koa
compose:
function compose(middlewares){
return ctx => {
const dispatch = i => {
const middleware = middlewares[i]
if(!middleware) return
return middleware(ctx, () => dispatch(i+1))
}
return dispatch(0)
}
}
Context: ctx
class Context{
constructor(req, res) {
this.req=req
this.res=res
}
}
Koa-mini
class Application() {
constructor() {
this.middlewares = []
}
listen(...args) {
const server = http.createServer(async (req,res) => {
const ctx = new Context(req,res)
const fn = compose(this.middlewares)
await fn(ctx)
ctx.res.end(ctx.body)
})
server.listen(...args)
}
use(middleware){
this.middlewares.push(middleware)
}
}
本人才疏学浅,文中难免有不妥错误之处,还望同学们批评指正,感激不尽!