龙空技术网

JavaScript运行原理:浏览器事件循环机制(Event Loop),超详细

沪漂码农小邹 1555

前言:

目前咱们对“js定时循环执行”大致比较关心,兄弟们都需要剖析一些“js定时循环执行”的相关资讯。那么小编也在网摘上汇集了一些有关“js定时循环执行””的相关知识,希望小伙伴们能喜欢,看官们快快来了解一下吧!

浏览器内核中有一个渲染进程,又名Renderer进程,我们页面的渲染,js的执行,事件的循环都在这一进程内进行,也就是说,该进程下面拥有着多个线程,靠着这些现成共同完成渲染任务。这个进程里面主要有一下几个线程:

1、 图形用户界面GUI渲染线程

负责渲染浏览器界面,包括解析HTML、CSS、构建DOM树、Render树、布局与绘制等当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行与JS执行线程互斥。浏览器为了保持render树结构的稳定,当JS执行时会停止页面的渲染。

2、JS引擎执行线程

JS内核,也称JS引擎,负责处理执行javascript脚本等待任务队列的任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS引擎在运行JS程序,因为是单线程的。

3、定时触发器线程

setInterval与setTimeout所在线程定时计时器并不是由JS引擎计时的,因为如果JS引擎是单线程的,如果JS引擎处于堵塞状态,那会影响到计时的准确当计时完成被触发,事件会被添加到事件队列,等待JS引擎空闲了执行注意:W3C的HTML标准中规定,setTimeout中低于4ms的时间间隔算为4ms

4、异步HTTP请求线程

在XMLHttpRequest在连接后新启动的一个线程线程如果检测到请求的状态变更,如果设置有回调函数,该线程会把回调函数添加到事件队列,同理,等待JS引擎空闲了执行

5、事件触发线程

听起来像JS的执行,但是其实归属于浏览器,而不是JS引擎,用来控制时间循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)当JS引擎执行代码块如setTimeout时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理注意:由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

浏览器Event Loop图:

示例代码:

1. 初始化状态都为空,浏览器控制台是空的的,调用堆栈也是空的

2. console.log(‘Hi’)添加到调用堆栈中

3. 执行console.log(‘Hi’)

4. console.log(‘Hi’)从调用堆栈中移除。

5. setTimeout(function cb1() { … }) 添加到调用堆栈。

6. setTimeout(function cb1() { … }) 执行,浏览器创建一个计时器计时,这个作为Web api的一部分。

7. setTimeout(function cb1() { … })本身执行完成,并从调用堆栈中删除。

8. console.log(‘Bye’) 添加到调用堆栈

9. 执行 console.log(‘Bye’)

10. console.log(‘Bye’) 从调用调用堆栈移除

11. 至少在5秒之后,计时器完成并将cb1回调推到回调队列。

12. 事件循环从回调队列中获取cb1并将其推入调用堆栈。

13. 执行cb1并将console.log(‘cb1’)添加到调用堆栈。

14. 执行 console.log(‘cb1’)

15. console.log(‘cb1’) 从调用堆栈中移除

16. cb1 从调用堆栈中移除

快速回顾:

宏任务和微任务

在JavaScript中,任务被分为Task(又称为MacroTask,宏任务)和MicroTask(微任务)两种。它们分别包含以下内容:

MacroTask: script(整体代码), setTimeout, setInterval, setImmediate(node独有), I/O, UI rendering

MicroTask: process.nextTick(node独有), Promises, Object.observe(废弃), MutationObserver

需要注意的一点是:

在同一个上下文中,总的执行顺序为同步代码—>microTask—>macroTask

具体来说,浏览器会不断从task队列中按顺序取task执行,每执行完一个task都会检查microtask队列是否为空(执行完一个task的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去task队列中取下一个task执行,以此类推。

示例代码:

主程序和和settimeout都是宏任务,两个promise是微任务

第一个宏任务(主程序)执行完,执行全部的微任务(两个promise),再执行下一个宏任务(settimeout),所以结果为:

正确答案是

script start, script end, promise1, promise2, setTimeout

标签: #js定时循环执行