浏览器事件循环机制(Event Loop)(上)

482 阅读4分钟

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

前言

JS是单线程的,JS是异步的。那单线程是如何做到异步的? 事件循环机制又是如何处理的,该内容涉及点较多,因此分为上下两篇来展开介绍,在上中,主要去理解js中涉及的重要概念。

相关概念

在回答前言中的问题之前,先了解几个概念。

  • 进程 在看过的一篇文章中举了这样一个形象的例子,工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间必须停工。在计算机中,意思就是单个CPU一次只能运行一个任务。对于计算机而言,每一个应用程序都是一个进程,每个应用程序都会有很多功能模块,这些功能模块实际上是通过子进程来实现的。对于这种子进程的扩展方式,可以称这个应用程序是多进程的。

进程 就像工厂的车间,它代表CPU所能处理的单个任务。进程之间相互独立,任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。CPU使用时间片轮转进度算法来实现同时运行多个进程。

  • 线程接上面进程的例子继续,在每个车间里,可以有很多工人,共享车间所有的资源,他们协同完成一个任务。

线程就好比车间里的工人,一个进程可以包括多个线程,多个线程共享进程资源。

  • 单线程js引擎中负责解析执行js代码的线程只有一个(主线程)。 js是单线程的,意思是说每次只能做一件事,一个ajax请求,主线程在等待他响应的同时还会去做其他事情,浏览器先在事件表注册ajax的回调函数,响应回来后回调函数被添加到任务队列中等待执行,不会造成线程阻塞,所以说js处理ajax请求的方式是异步的。

  • 异步js的任务分为同步异步同步任务是直接在主线程上排队执行,只有前一个任务执行完毕,才能执行后一个任务。异步任务则会被放到任务队列中,若有多个异步任务,则要在任务队列中排队等待,任务队列类似一个缓冲区,任务下一步会被移到调用栈,然后主线程执行调用栈的任务(异步任务不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行)。

  • 事件循环检查调用栈是否为空,以及确定把哪个task加入调用栈的这个过程就是事件循环,而js实现异步的核心就是事件循环。 -调用栈是一个栈结构,函数调用会形成一个栈帧,帧中包含了当前执行函数的参数和局部变量等上下文信息,函数执行完后,它的执行上下文会从栈中弹出。 -宏任务包含执行整体的js代码,事件回调,XHR回调,定时器(setTimeout/setInterval/setImmediate),IO操作,UI render。 -微任务更新应用程序状态的任务,包括promise回调,MutationObserver,process.nextTick,Object.observe。

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

image.png

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

总结

作为事件循环机制的上篇,主要介绍了相关概念,那事件循环机制到底是怎样的一个过程,将在下中具体讲解。