渲染流程会做什么?
为什么需要eventLoop?
js引擎的编译流水线是什么?
JS宿主环境可能会有哪些不同?
requestAnimationFrame是宏任务还是微任务?
micro task 和 check解决了什么样的问题?
JS引擎的组成
js引擎包括parser、解释器、gc再加上JIT编译器
parse: 负责将源码转成AST
interperter: 解释器,负责转换AST成字节码,并且解释执行
JIT compiler: 对执行时的热点函数进行收集然后编译,把字节码转换成机器码,之后就可以直接执行机器码
gc(garbage collector): 垃圾回收器, 清理堆内存中不再使用的对象。
编译流水线
一般的 JS 引擎的编译流水线是 parse 源码成 AST,之后 AST 转为字节码,解释执行字节码。运行时会收集函数执行的频率,对于到达了一定阈值的热点代码,会把对应的字节码转成机器码(JIT),然后直接执行。这就是 js 代码能够生效的流程。
渲染引擎流程做了什么??
渲染时会把 html、css 分别用 parser 解析成 dom 和 cssom,然后合并到一起,并计算布局样式成绝对的坐标,生成渲染树,之后把渲染树的内容复制到显存就可以由显卡来完成渲染。
JS引擎和渲染引擎如何配合呢??
JS 引擎只会不断执行 JS 代码,渲染引擎也是只会布局和渲染。要怎么综合两者呢?
两种思路
多线程
分为多个线程,主线程用来操作 ui 和渲染,其他线程用来执行一些任务(不能多个线程同时修改 ui,顺序没法控制)。
单线程
js最开始的设计知识用来做表单处理,就没有采用多线程架构,而且js设置为单线程的原因是为了防止DOM冲突的问题,那么需要将js执行和ui渲染还有一些需要异步来处理的事情就需要一个机制来实现,浏览器对这个机制的实现方案就是Event Loop。
不同的宿主环境中对Event Loop的实现是不同的:
1.浏览器主要是调度渲染和js各项任务的执行,还有worker
2.node里面主要是调度各种io
浏览器的Event Loop
渲染,js, worker的调度问题
每次loop会执行js一个宏任务,然后执行所有现有的微任务,然后需要check是不是需要渲染,然后会check一下是不是需要处理worker的信息。微任务就是js中需要及时办的任务,队列中要是有了微任务到有时间解决了就需要一次性的全部解决掉。
js计算与渲染的冲突问题
每一帧的计算和渲染是有固定频率的,如果 JS 执行时间过长,超过了一帧的刷新时间,那么就会导致渲染延迟,甚至掉帧(因为上一帧的数据还没渲染到界面就被覆盖成新的数据了),给用户的感受就是“界面卡了”。
Event Loop的每一个阶段都有可能会发生因为js计算的掉帧问题,那我们就要控制js的计算量,除此之外浏览器也提供了API让我们能够将计算做拆分来执行,合理的调度api就会使页面渲染效果变好,有requestAnimationFrame和requestIdleCallback两个API
渲染之前的一个回调函数(requestAnimationFrame)
requestAnimationFrame 是每次 loop 结束发现需要渲染,在渲染之前执行的一个回调函数,不是宏微任务。
看时机而执行的API(requestIdleCallback)
requestIdleCallback 会在每次 check 结束发现距离下一帧的刷新还有时间,就执行一下这个。如果时间不够,就下一帧再说。那如果一直没时间执行呢?那就需要强制执行了,也就是说把需要进行计算的代码放到这个API里面一直得不到执行,那影响了计算的进行也不合理,所以提供了 timeout 的参数可以指定最长的等待时间,如果一直没时间执行这个逻辑,那么就算拖延了帧渲染也要执行。
总结
event loop 是宿主环境为了集合渲染和 JS 执行,也为了处理 JS 执行时的高优先级任务而设计的机制。 loop 内的逻辑执行不能阻塞 check,也就是不能阻塞渲染引擎做帧刷新。所以不管是 JS 代码宏微任务、 requestAnimationCallback、requestIdleCallback 都不能计算时间太长。这个问题是前端开发需要考虑的问题。