js 基础 - 事件循环 & 垃圾回收机制
2023-10-24 04:53:11
# fontend
1. 什么是事件循环(EventLoop)
为了解决单个任务执行时间过长和处理高优先级的任务,所以需要将任务划分, 引入同步任务和异步任务解决执行时间过长的问题,同步任务在执行栈中直接执行,异步任务放入任务队列等待执行;为了解决异步队列中等待任务的执行优先级问题,将异步任务划分为宏任务和微任务,同步任务执行完后,先执行微任务,然后执行宏任务,以此循环,形成事件循环。
2. 宏任务(macrotask) & 微任务(microtask)
- 微任务: 微任务仅来自于我们的代码,通常由 promise 创建,优先级比宏任务高,属于异步任务。
- 宏任务: 一般的异步任务。
可以创建微任务的:
- promise
- async await
- queueMicrotask
可以创建宏任务的:
- setTimeout / setInterval
- script 标签脚本
- 事件回调函数: DOM Events, I/O, requestAnimationFrame 等
3. 异步任务相关的面试题
1 | // 1 考察 promise 和 async |
3. 垃圾回收机制(garbage collection)
为什么需要垃圾回收:
因为 js 运行期间会生成变量,函数这些占用内存空间的存储,如果这些内存不及时释放,则会造成内存过大,js 运行过载的情况。所以不再使用的变量活着函数需要被及时回收,以免造成内存泄漏。
回收机制原理
- 标记清理:被执行时标记存在上下文,执行完毕清除标记,被回收
- 优点: 实现比较简单
- 缺点: 清除之后,剩余对象的内存位置不变,会导致空闲内存空间不是连续的,会牵扯出内存分配效率慢等问题
- 引用计数:追踪值被引用的次数,当引用次数为 0 的时候则被回收,闭包的原理就是因为函数中引用的变量计数没有归 0 所以不会被回收
- 优点: 可以立即回收,标记清理需要定期清理,另外标记需要遍历,引用则不需要
- 缺点: 引用计数需要一个计数器,会占用很多内存;无法解决循环引用无法回收的问题
v8 针对 gc 做出的优化
- 分代式回收
- 针对变量或对象存活的时间长短,大小,生成时间的新旧来区分为新生代和老生代,在存储上也分别存在不同的空间,新生代分配的内存空间 1-8M 左右,对应的 gc 算法是 scavenge 效率高,老生代占用的内存偏大,对应采用的是 mark-compact-sweep,效率低些
- scavenge 算法: 其具体实现主要采用了 Cheney 算法,将新生代的堆内存空间分为 处于使用中的 from 空间和处于闲置的 to 空间,当分配对象时,会先放到from 空间中。垃圾回收时遍历 from 空间,将还存活的对象复制到 to 空间,然后 from 和 to 空间角色互换。当一个对象经过多次复制依然存活的时候,就会被晋升到老生代内存空间中。
- mark- sweep & mark-compact 算法:
- mark- sweep: 标记清除,定期清除不在存活的对象
- mark-compact: 标记整理,就是在清除之后,如果发现老生代分配空间不足时,对新生代晋升过来的对象分配时采用 mark-compact 对内存空间重新分配
- 并行回收: 因为 js 是单线程运行的,gc 运行时也是会阻塞 js 执行的,为了加快 gc 回收,引入了并行回收,开启多个辅助线程,协同完成 gc 回收工作
- 并发回收:采用并行回收还是会多少存在 js 脚本阻塞的问题,为了从根本上解决这个问题,采用了并发回收机制,gc 回收完全在辅助线程上进行,不占用主线程,丝毫不会导致阻塞 js 脚本。但是要实现并发很难,主线程在执行 js 的时候,堆中的对象引用关系随时在变化,所以辅助线程的标记也会改变,所以需要额外实现一些读写锁的机制来控制。