We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
js 引擎只包括 parser、解释器、gc 再加一个 JIT 编译器这几部分,它的编译流水线就是 parse 源码成 AST, AST 到 字节码,解释字节码执行,运行时会收集函数执行的频率,热点代码的字节码转成机器码执行(JIT)。 这就是 js 代码能够生效的全部流程了。js 引擎内部并没有创建线程,也就是说跑在宿主线程上。 宿主可能有很多,比如浏览器需要用 JS 引擎、node 也需要,跨端引擎也需要,总之一切调用 JS 引擎的地方所在的线程就是 JS 引擎的线程,线程是 CPU 的一条执行路径(控制流),一个线程有一条控制流。但是浏览器是一个涉及到 ui 的软件,需要交互,也就需要经常响应用户的事件,这种单线程方式肯定不行。或者给 JS 引擎注入线程的全局 API,或者提供一套调度机制,其实这两者一个是操作系统调度(线程),一个是浏览器自己调度(任务),都差不多。注入线程 API 的方式比较复杂,因为多个线程修改 ui 那么 ui 应该显示啥,所以默认都会只有主线程可以操作 ui,安卓中就是这样的,子线程写逻辑。 浏览器中也提供了子线程api,只用于执行逻辑,所以叫 worker 进程,但是那是后来的事情了。最开始的方式还是提供自己的任务队列和调度机制。这就是 event loop。
event loop 是通过一个队列保存要处理的任务,这个思路和后端的消息队列一样的,一个任务一个任务取出来执行,后来的任务要排队,但是有一点不同,浏览器里 js 要处理交互,经常有急事,必须要插队,于是就每个任务之后把所有的急事处理一下,也就是 task 执行完之后执行所有的 microtask 的原因。
event loop 是宿主环境提供的,为了更好的处理交互,不同的宿主环境面对的问题不同,所以会有不同的 event loop 实现,浏览器里面要做渲染,所以每个 event loop 都要去检查是否需要把线程阻塞,让渲染线程去执行,node 里的 event loop 需要考虑的问题是 io 的回调,所以每次执行完任务都要去检查下是否要处理 io回调。 跨端引擎也会自己实现 event loop,可以在每次 loop(执行完一个task)之后去做一些 check,来执行一些要求紧急处理的事情。这里的急事有两方面,一方面是 js 里面的“急事”,通过 microtask 支持,一种是其他的“急事”,通过每次 loop 结束的 check 来支持。
这里只谈浏览器的 event loop,那么处理 microtask 处理交互上的急事之外,每次 loop 结束要去 check 的是渲染,这是其他的急事。 渲染并不是 JS 引擎做的,是在渲染引擎,它有自己的执行频率,就是帧率,每一帧要计算样式成坐标,然后渲染,就像 cpu 每个指令周期都做取指令、译码、执行指令一样,帧刷新(执行下一帧)和指令执行(受 cpu 主频控制)都比较纯粹。渲染引擎只会不断循环刷新帧(按照一定的频率,一般一秒 60 帧),它并不知道什么时候要去执行 JS,同样 JS 引擎也只知道解释 JS,并不知道什么时候去渲染,综合这两者的方式就是定制 event loop。所以每次 event loop 结束之后要去 check 是否要渲染,并且切到渲染做帧刷新之前,还有个生命周期,也就是 requestAnimationCaback,这个 api 很自然,一般这种切换都会有生命周期函数的。
帧的刷新要保证一定的频率才可以,不然渲染的画面就会卡顿(也就是拖延了刷新时间),这个拖延的时间如果超过了每帧的间隔时间,那么就会掉帧了,因为上一帧的数据还没渲染到界面就被覆盖成新的数据了,这时候页面就会出现顿感。什么时候会导致帧刷新拖延甚至帧数据被覆盖呢(丢帧),明显是当某个 task 或者 microtask 执行时间太长,都到不了 check 阶段,这样等到了 check 的时候发现要渲染了,再去渲染的时候就晚了。 所以 JS 代码每个 task 不要做太多的计算,要做拆分,这也是为啥 ui 框架要做计算的 fiber 化,就是因为处理交互的时候,不能让计算阻塞了渲染,要递归改循环,通过链表来做计算的暂停恢复。 除了 JS 代码要注意之外,如果浏览器能够提供 API 就是在每帧间隔来执行,那样岂不是就不会阻塞了,所以后来有了 requestIdeCallback,这个 api 会在每次 check 结束发现里下一帧的刷新还有时间,就执行一下这个。如果时间不够,就下一帧再说。如果每一帧都没时间呢,那也不行,所以提供了 timeout 的参数可以指定最长的等待时间,如果一直没时间执行这个逻辑,那么就算拖延了帧渲染也要执行。 这个 api 对于前端框架来说太需要了,框架就是希望计算不阻塞渲染,也就是在每一帧的间隔时间(idle时间)做计算,但是这个 api 毕竟是最近加的,有兼容问题,所以 react 自己实现了类似 idle callback 的fiber 机制,在执行逻辑之前判断一下离下一帧刷新还有多久,来判断是否执行逻辑。
总之,浏览器里有 JS 引擎做 JS 代码的执行,利用注入的浏览器 API 完成功能,有渲染引擎做页面渲染,两者都比较纯粹,需要一个调度的方式就是 event loop,event loop 实现了 task 和 急事处理机制 microtask,而且每次 loop 结束会 check 是否要渲染,渲染之前会有 requestAnimationFrames 生命周期。 帧刷新不能被拖延否则会卡顿甚至掉帧,所以就需要 JS 代码里面不要做过多计算,于是有了 requestIdleCallback 的 api,希望在每次 check 完发现还有时间就执行,没时间就不执行(这个deadline的时间也作为参数让 js 代码自己判断),为了避免一直没时间,还提供了 timeout 参数强制执行。 这个问题可以说是浏览器相关开发里最核心的部分,就是怎么不阻塞渲染,让逻辑能够拆成帧间隔时间内能够执行完的小块,浏览器提供了 idelcallback 的 api,很多 UI 框架也通过递归改循环然后记录状态等方式实现了计算量的拆分,目的只有一个,loop 内的逻辑执行不能阻塞 check,也就是不能阻塞渲染引擎做帧刷新。所以不管是 JS 代码宏微任务、 requestAnimationCallback、requestIdleCallback 都不能计算时间太长。这个问题是前端开发的持续性阵痛。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
js 引擎只包括 parser、解释器、gc 再加一个 JIT 编译器这几部分,它的编译流水线就是 parse 源码成 AST, AST 到 字节码,解释字节码执行,运行时会收集函数执行的频率,热点代码的字节码转成机器码执行(JIT)。 这就是 js 代码能够生效的全部流程了。js 引擎内部并没有创建线程,也就是说跑在宿主线程上。 宿主可能有很多,比如浏览器需要用 JS 引擎、node 也需要,跨端引擎也需要,总之一切调用 JS 引擎的地方所在的线程就是 JS 引擎的线程,线程是 CPU 的一条执行路径(控制流),一个线程有一条控制流。但是浏览器是一个涉及到 ui 的软件,需要交互,也就需要经常响应用户的事件,这种单线程方式肯定不行。或者给 JS 引擎注入线程的全局 API,或者提供一套调度机制,其实这两者一个是操作系统调度(线程),一个是浏览器自己调度(任务),都差不多。注入线程 API 的方式比较复杂,因为多个线程修改 ui 那么 ui 应该显示啥,所以默认都会只有主线程可以操作 ui,安卓中就是这样的,子线程写逻辑。 浏览器中也提供了子线程api,只用于执行逻辑,所以叫 worker 进程,但是那是后来的事情了。最开始的方式还是提供自己的任务队列和调度机制。这就是 event loop。
event loop 是通过一个队列保存要处理的任务,这个思路和后端的消息队列一样的,一个任务一个任务取出来执行,后来的任务要排队,但是有一点不同,浏览器里 js 要处理交互,经常有急事,必须要插队,于是就每个任务之后把所有的急事处理一下,也就是 task 执行完之后执行所有的 microtask 的原因。
event loop 是宿主环境提供的,为了更好的处理交互,不同的宿主环境面对的问题不同,所以会有不同的 event loop 实现,浏览器里面要做渲染,所以每个 event loop 都要去检查是否需要把线程阻塞,让渲染线程去执行,node 里的 event loop 需要考虑的问题是 io 的回调,所以每次执行完任务都要去检查下是否要处理 io回调。 跨端引擎也会自己实现 event loop,可以在每次 loop(执行完一个task)之后去做一些 check,来执行一些要求紧急处理的事情。这里的急事有两方面,一方面是 js 里面的“急事”,通过 microtask 支持,一种是其他的“急事”,通过每次 loop 结束的 check 来支持。
这里只谈浏览器的 event loop,那么处理 microtask 处理交互上的急事之外,每次 loop 结束要去 check 的是渲染,这是其他的急事。 渲染并不是 JS 引擎做的,是在渲染引擎,它有自己的执行频率,就是帧率,每一帧要计算样式成坐标,然后渲染,就像 cpu 每个指令周期都做取指令、译码、执行指令一样,帧刷新(执行下一帧)和指令执行(受 cpu 主频控制)都比较纯粹。渲染引擎只会不断循环刷新帧(按照一定的频率,一般一秒 60 帧),它并不知道什么时候要去执行 JS,同样 JS 引擎也只知道解释 JS,并不知道什么时候去渲染,综合这两者的方式就是定制 event loop。所以每次 event loop 结束之后要去 check 是否要渲染,并且切到渲染做帧刷新之前,还有个生命周期,也就是 requestAnimationCaback,这个 api 很自然,一般这种切换都会有生命周期函数的。
帧的刷新要保证一定的频率才可以,不然渲染的画面就会卡顿(也就是拖延了刷新时间),这个拖延的时间如果超过了每帧的间隔时间,那么就会掉帧了,因为上一帧的数据还没渲染到界面就被覆盖成新的数据了,这时候页面就会出现顿感。什么时候会导致帧刷新拖延甚至帧数据被覆盖呢(丢帧),明显是当某个 task 或者 microtask 执行时间太长,都到不了 check 阶段,这样等到了 check 的时候发现要渲染了,再去渲染的时候就晚了。 所以 JS 代码每个 task 不要做太多的计算,要做拆分,这也是为啥 ui 框架要做计算的 fiber 化,就是因为处理交互的时候,不能让计算阻塞了渲染,要递归改循环,通过链表来做计算的暂停恢复。 除了 JS 代码要注意之外,如果浏览器能够提供 API 就是在每帧间隔来执行,那样岂不是就不会阻塞了,所以后来有了 requestIdeCallback,这个 api 会在每次 check 结束发现里下一帧的刷新还有时间,就执行一下这个。如果时间不够,就下一帧再说。如果每一帧都没时间呢,那也不行,所以提供了 timeout 的参数可以指定最长的等待时间,如果一直没时间执行这个逻辑,那么就算拖延了帧渲染也要执行。 这个 api 对于前端框架来说太需要了,框架就是希望计算不阻塞渲染,也就是在每一帧的间隔时间(idle时间)做计算,但是这个 api 毕竟是最近加的,有兼容问题,所以 react 自己实现了类似 idle callback 的fiber 机制,在执行逻辑之前判断一下离下一帧刷新还有多久,来判断是否执行逻辑。
总之,浏览器里有 JS 引擎做 JS 代码的执行,利用注入的浏览器 API 完成功能,有渲染引擎做页面渲染,两者都比较纯粹,需要一个调度的方式就是 event loop,event loop 实现了 task 和 急事处理机制 microtask,而且每次 loop 结束会 check 是否要渲染,渲染之前会有 requestAnimationFrames 生命周期。 帧刷新不能被拖延否则会卡顿甚至掉帧,所以就需要 JS 代码里面不要做过多计算,于是有了 requestIdleCallback 的 api,希望在每次 check 完发现还有时间就执行,没时间就不执行(这个deadline的时间也作为参数让 js 代码自己判断),为了避免一直没时间,还提供了 timeout 参数强制执行。 这个问题可以说是浏览器相关开发里最核心的部分,就是怎么不阻塞渲染,让逻辑能够拆成帧间隔时间内能够执行完的小块,浏览器提供了 idelcallback 的 api,很多 UI 框架也通过递归改循环然后记录状态等方式实现了计算量的拆分,目的只有一个,loop 内的逻辑执行不能阻塞 check,也就是不能阻塞渲染引擎做帧刷新。所以不管是 JS 代码宏微任务、 requestAnimationCallback、requestIdleCallback 都不能计算时间太长。这个问题是前端开发的持续性阵痛。
The text was updated successfully, but these errors were encountered: