【JS系列】深入探究事件循环机制Event loop

0. 背景   

目录

0. 背景   JavaScript单线程本质

1. 函数调用栈与任务队列

2. 从setTimeout看事件循环机制

3. 任务队列类型

​​


JavaScript单线程本质

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。例如死锁问题或者访问临界区资源问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?(其实这涉及到对DOM的互斥访问问题)

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

1. 函数调用栈与任务队列

1.1 Javascript处理事件的大致流程

Javascript有一个main thread 主进程和call-stack(一个调用堆栈),在对一个调用堆栈中的task处理的时候,其他的都要等着。当在执行过程中遇到一些类似于setTimeout等异步操作的时候,会交给浏览器的其他模块(以webkit为例,是webcore模块)进行处理,当到达setTimeout指定的延时执行的时间之后,task(回调函数)会放入到任务队列之中。一般不同的异步任务的回调函数会放入不同的任务队列之中。等到调用栈中所有task执行完毕之后,接着去执行任务队列之中的task(回调函数)。

在上图中,调用栈中遇到DOM操作、ajax请求以及setTimeout等WebAPIs的时候就会交给浏览器内核的其他模块进行处理,webkit内核在Javasctipt执行引擎之外,有一个重要的模块是webcore模块。对于图中WebAPIs提到的三种API,webcore分别提供了DOM Binding、network、timer模块来处理底层实现。等到这些模块处理完这些操作的时候将回调函数放入任务队列中,之后等栈中的task执行完之后再去执行任务队列之中的回调函数。

2. 从setTimeout看事件循环机制

2.1 setTimeout示例

下面用Philip Roberts的演讲中的一个栗子来说明事件循环机制究竟是怎么执行setTimeout的。

                                                              

① 首先main()函数的执行上下文入栈

代码接着执行,遇到console.log(‘Hi’),此时log(‘Hi’)入栈,console.log方法只是一个webkit内核支持的普通的方法,所以log(‘Hi’)方法立即被执行。此时输出’Hi’。

                                                                 

②遇到setTimeout的时候,执行引擎将其添加到栈中。

调用栈发现setTimeout是之前提到的WebAPIs中的API,因此将其出栈之后将延时执行的函数交给浏览器的timer模块进行处理

        

③timer模块去处理延时执行的函数、继续执行引擎、接着执行将log(‘SJS’)添加到栈中,此时输出’SJS’

④当timer模块中延时方法规定的时间到了之后就将其放入到任务队列之中,此时调用栈中的task已经全部执行完毕

        

⑤调用栈中的task执行完毕之后,执行引擎会接着看执行任务队列中是否有需要执行的回调函数。这里的cb函数被执行引擎添加到调用栈中,接着执行里面的代码,输出’there’。等到执行结束之后再出栈。

2.2总结流程:

  • 1.所有的代码都要通过函数调用栈中调用执行。即所有同步任务都在main thread主线程上执行,形成一个执行栈(execution context stack)。
  • 2.当遇到前文中提到的APIs的时候,会交给浏览器内核的其他模块进行处理。
  • 3.只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  • 4.任务队列中存的都是回调函数。
  • 5.一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • 6.主线程不断重复上面的第5步

                                                                            

只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

3. 任务队列类型

js 是单线程执行的,将任务分为了同步任务异步任务;而异步任务又可以分为微任务和宏任务。

浏览器中的事件循环的任务队列被划分为宏任务和微任务两种类型

macrotask(宏任务):是指消息队列中的等待被主线程执行的事件,宏任务执行时都会重新创建栈,然后调用宏任务中的函数,栈也会随着变化,但宏任务执行结束时,栈也会随之销毁。

包含执行整体的js代码script,事件回调,XHR回调,定时器(setTimeoutsetInterval、 setImmediate),IO操作,UI render

microtask(微任务):可以把微任务看成是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

更新应用程序状态的任务,包括promise回调,MutationObserver(html5的新特性)process.nextTickObject.observe(已弃用)

区别:

宏任务和微任务的区别在于在事件循环机制中,执行的机制不同

每次执行完所有的同步任务后,会在任务队列中取出异步任务,先将所有微任务执行完成后,才会执行宏任务 所以可以得出结论, 微任务会在宏任务之前执行。 我们在工作常用到的宏任务是 setTimeout,而微任务是 Promise.then

希望对你们有帮助~^_^觉得有用点个赞呗~~

后续会把各个系列整理成文档分享给大家。有需要关注我。

猜你喜欢

转载自blog.csdn.net/weixin_41950078/article/details/115175942