温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,汇文网负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
网站客服:3074922707
分析
浏览器
底层
事件
循环
处理
机制
邓杰海
2023.7电脑编程技巧与维护图2事件循环处理过程1概述近年,随着连接互联网越来越便利、网络速度越来越快,除一些特定的行业领域,以前基于C/S架构的桌面应用程序基本上都迁移到基于互联网浏览器的应用开发上。现在前端浏览器的应用程序开发与以前传统的前端开发有很大不同,前端软件的功能越来越复杂,开发架构也非常多,例如,Vue、React和Angular等,不管使用哪种架构进行前端应用程序开发,浏览器底层对各种消息事件处理逻辑都是一致的。因此,要想熟练地开发各种前端应用,就必须熟悉JS引擎对各种消息事件的处理过程。2浏览器的架构及渲染进程的事件循环以谷歌公司的开源Chromium浏览器为例,说明浏览器的进程结构,如图1所示。Chromium有一个浏览器主进程Brower,它负责管理整个浏览器界面,启动网络进程Net、负责处理底层显示驱动的GPU进程、第三方插件进程Plugin和渲染进程Renderer,每打开一个网页标签就启动一个渲染进程Renderer,渲染进程之间不会相互影响,也就是说如果某个网页在操作过程中崩溃了,则不会影响别的网页标签运行,整个浏览器还可以正常运行。如图1所示,渲染进程在启动过程中,会分别启动JS引擎线程、Event事件触发线程、定时器(Timer)触发线程、HTTP异步请求线程和负责渲染的GUI线程。其中,谷歌公司的高性能JS引擎就在JS引擎线程中运行,此线程负责执行同步代码或从任务队列中提取异步回调函数。JS引擎线程的事件循环处理过程如图2所示,JS引擎线程先执行同步代码,遇到异步API函数,将异步API函数发送至如图1所示的相应线程。例如,处 理Promise对 象 时,先 执 行 里 面 的 同 步 代 码,当Promise对象状态由pending变为fulfilled或rejected时,将then()或catch()程序段中的回调处理函数由事件触发线程加入微任务队列中。JS引擎线程执行setTimeout或setInterval API函数时,将定时器setTimeout或set-Interval API发送至定时器触发线程。定时器触发线程只负责计时,不会处理对应的回调函数,当定时时间到,定时器触发线程会将回调函数发送至事件触发线程,由事件触发线程将回调函数加入宏任务队列中。基金项目:江西省教育厅科学技术研究项目重点项目(项目编号:GJJ213301)。作者简介:邓杰海(1975),男,副教授,高级工程师,硕士,研究方向为软件开发、数据库编程等。分析浏览器底层事件循环处理机制邓杰海,刘志强,房宏焘(江西中医药高等专科学校网络与教育技术中心,江西 抚州344000)摘要:进行前端浏览器程序开发时,在编写同步代码的同时更多的工作量是异步程序代码的编写,例如,定时器回调函数、各种用户界面(UI)事件触发函数、XMLHttpRequest 请求回调函数、Promise执行成功及失败的处理函数等,如果这些回调函数同时发生,那么浏览器进程将按照怎样的优先顺序来处理这些回调函数呢?通过分享 HTML5 规范文档、编写程序,分析了宏任务中多个定时器的运行机制及微任务中 Promise 和 Mutation Observer 执行的优先顺序。关键词:JavaScript 语言;HTML5 规范;Event Loops 事件处理;Promise 对象;Mutation Observer 事件;node.js 架构;XMLHttpRequest 对象图1Chromium进程结构HTTP异步请求线程浏览器进程渲染进程Renderer网络进程NetGPU进程第三方插件进程PluginJS引擎线程Event事件触发线程Timer触发线程GUI线程开始Event loops循环执行同步代码NNYYY宏任务队列是否存在任务?微任务队列是否存在任务?执行一个宏任务Y微任务队列是否存在任务?执行微任务队列任务微任务队列是否全部执行完?重新渲染开始下一次循环NN11DOI:10.16184/prg.2023.07.0262023.7电脑编程技巧与维护图4宏任务运行分析3程序运行分析为了分析宏任务setTimeout、XMLHttpRequest和UI事件触发处理onbuttonClick()之间是否存在优先级、微任务Promise及MutationObserver之间是否存在优先级、宏任务与微任务之间的是否存在优先级,编写node.js上下文环境的服务端程序NodeServerDjh.js和前端网页test2.html,运行环境分别为node.js V14.16.1、谷歌浏览器109.0.5414.120(正 式 版 本)(64位),使 用nodeNodeServerDjh.js命令运行node.js服务端程序。(1)分析宏任务setTimeout、XMLHttpRequest和UI事件触发处理onbuttonClick()之间是否存在优先级。浏览器打开网页的运行结果如图3所示,通过图3右侧的输出可以发现,程序清单中第56行Button按钮自动触发的onclick事件的输出,居然先于程序清单中第108行 的 同 步 代 码 运 行,难 道Button按 钮 自 动 触 发 的onclick事件处理函数是同步运行而不是异步运行?为了验证这个问题,对Button按钮添加自定义事件butcusev-ent,见程序清单第59行的butCusEvt函数,将butCu-sEvt函数加入一个setTimeout的回调函数中,同时在这个回调函数中添加一条console.log语句,见程序清单第89行。在图3的右侧可以看出,Button按钮添加自定义事件butcusevent的输出先于程序清单第89行的输出,由此可以确定Button按钮自动触发的事件处理函数是同步运行而不是异步运行。运行附件的所带的程序清单,通过反复几次修改第75行定时器的时间,可以得出宏任务setTimeout、XML-HttpRequest两者之间没有优先顺序,先满足条件先执行。从图3可以看出,XMLHttpRequest的回调函数中console.log语句执行大概花了27 ms。将程序清单中第75行的定时器的延时改为40 ms,可以得到图4运行结果中右侧带有下划线的输出部分。(2)分析微任务Promise及MutationObserver之间是否存在优先级。查看大量资料都说明微任务地执行存在优先级,其中,Promise的优先级高于MutationObserver。事实上通过编程,反复修改程序中的程序代码,得到的不是这样的结果。为了保证这两个微任务开始执行的时机一致,在同一次Event loop循环中执行,在程序清单中第32行、41行分别定义了两个函数MO和promi,并将这两个函数加入第75行定义的定时器的回调函数中。首先,将promi函数放在MO函数前面执行,见程序清单第81行和83行,得到的运行结果如图4所示,promi函数在第34 ms运行完,MO函数在第35 ms运行完,promi函数先于MO函数运行。然后,将MO函数放在promi函数前运行,可以得到如图5所示的运行结果中右侧带有红色下划线的输出部分,MO函数先于promi函数输出结果,在图4、图5左侧的id=divid2的div控件中渲染的内容也是如此,MO函数与promi函数,在前面先执行并输出结果,反复刷新浏览器,非常稳定地得到相同结果。(3)分析宏任务与微任务之间的执行情况,判断它们之间是否存在优先级。首先将MO函数与promi函数从第75行的setTimeout定时器的回调函数中提出来,放在同步代码相同的层级运行。可以得出如图6所示的运行结果,在图6右侧输出结果中红色方框围住部分就是两个微任务输出的结果。从图6可以看出,除了同步代码及Button按钮的onclick事件先于两个微任务输出,其他的输出都是在两个微任务之后,这说明了Button按钮的onclick事件是同步执行而不是异步执行,同时也表明在本次Event loop循环处理中,得到的运行结果与图2是相一致的,首先,执行同步代码,再检测执行栈中有没有task,由于循环宏任务还没有被调度到执行栈中,检测微任务队列中有没有Job,如果有则将微任务队列中的Job全部执行完,最图3浏览器网页图5微任务运行分析122023.7电脑编程技巧与维护后渲染网页。要注意,JS引擎线程与渲染线程是互斥线程,如果JS引擎线程的程序代码执行时间过长,不能进行渲染,则浏览器界面会出现卡顿现象。(4)将MO与promi函数放置在第75行的setTime-out定时器的回调函数中,将这两个函数的运行层级与程序清单中的第77行定时器放在同一层级,比第87行的定时器低一层级运行。可以得出如图7所示的运行结果。与程序清单中第75行定时器相同层级的有第87行的定时器,比MO与promi函数要高一个层级;与这两个函数相同层级的有第77行定时器和第88行的Button控件自定义事件。从前面所述的事件循环处理过程可知,先执行同步代码,再执行一个宏任务,也就是执行程序清单中的第75行定时器中的同步代码,程序处理流程就会转到处理微任务队列,将微任务队列中所有Job处理完之后,对网页进行渲染,进入下一次循环处理,这与图7右侧所示的输出结果是相吻合的。为了再一次验证浏览器的处理机制,将第87行的定时器的程序代码移到第75行定义的定时器前面去执行,程序的执行结果如图8所示。从图8的执行结果可以得出,第87行的定时器回调函数先入队列,第75行的定时器回调函数被添加到队列的队尾,先执行完第87行定时器的回调函数,输出结果再次说明Button按钮的自定义事件butcusevent的处理函数是同步运行而不是异步运行。执行完第87行定时器的回调函数之后,检查微任务队列没有Job,进行网页渲染,进入下一次循环。在下一次循环处理第75行的定时器的回调函数,先执行同步代码,将MO和promi两个函数加入微任务队列,本次宏任务处理完之后,流程就转到处理微任务队列,直到所有的微任务队列全部被处理完,最后进行网页渲染,流程又进入下一次循环。3结语通过编程分析运行结果,可知Button控件的事件触发处理函数不是大多数资料所证明的异步执行,而是同步执行,无论业务规则和前端界面的设计多么复杂,在此所编写的程序代码都可以得出这一结论。也有很多资料说明微任务Promise的优先级要高于MutationObserver,但通过编程测试分析运行结果,发现这两个微任务之间没有优先级,而是代码在前面的先执行并输出结果。参考文献1David Flanagan JavaScript权 威 指 南M 6版.淘宝前端团队,译.北京:机械工业出版社,20172罗升阳 Chromium多进程架构简要介绍和学习计划EB/OL3未来科技 HTML5+CSS3从入门到精通(标准版)M 北京:中国水利水电版社,2017图6微任务先于宏任务运行图7微任务与宏任务同运行图8程序的执行结果13