【前端进阶】05 单线程的JavaScript如何管理任务的
单线程的JavaScript任务的管理
- JavaScript中的任务
- 同步任务与函数调用栈
- 异步任务和回调队列
- 事件循环(Event Loop)
JavaScript中的任务
JavaScript最初被设计为浏览器的脚本语言。
主要用途:对页面的操作、与浏览器的交互、与用户的交互、页面逻辑处理等。
如果将JavaScript设计成多线程,当多个线程对同一个DOM节点进行操作时,线程间的同步问题会变得非常复杂,为了避免这样的复杂性,JavaScript被设计为单线程。
单线程任务需要一个一个处理,如果有一个任务是等待用户输入,那么当用户操作前,所有任务就全部处于等待状态,页面就会呈现假死状态。为了高效的进行页面间的交互和渲染处理,围绕着任务执行是否阻塞主线程,将JavaScript中的任务分为同步任务和异步任务。
同步任务与函数调用栈
JavaScript在执行过程中每进入一个不同运行环境时,都会创建一个相应的执行上下文
JavaScript解释器会以栈的方式来管理这些执行上下文以及函数之间的关系,形成函数调用栈
调用栈可以理解为存储函数调用的栈结构,遵循先进后出的原则
异步任务和回调队列
异步任务会包括一些需要等待响应的任务,包括用户交互、http请求、定时器等
异步任务会提供回调函数,当异步任务有了运行结果之后,该任务则会被添加到回调队列中,主线程会在适当的时候从回调队列中取出相应的函数并执行
事件循环(Event Loop)
事件循环主要处理JavaScript中同步任务和异步任务的执行问题
根据运行环境不同,也会分为浏览器中的Event Loop和NodeJs中的Event Loop
浏览器中的Event Loop
在浏览器中,每当一个被监听的事件发生时,事件监听器绑定的相关任务就会被添加到回调队列中,通过事件产生的任务是异步任务,常见的有
事件循环机制
JavaScript的运行过程,如图:
主线程运行的时候,会产生堆和栈,其中堆为内存,栈为函数调用栈,Event Loop负责执行代码收集和处理事件以及执行队列中的子任务,具体包括以下过程
- JavaScript有一个主线程和调用栈,所有任务最终都会被放到调用栈等待主线程执行
- 同步任务会放到调用栈中,按照顺序等待主线程依次执行
- 主线程之外存在一个回调队列,回调队列中的异步任务最终在主线程中以调用栈的方式执行
- 同步任务都在主线程上执行,栈中代码在执行的时候调用浏览器的API,此时会产生异步任务
- 异步任务会在有了结果(比如被监听的事件发生时)之后,将异步任务已经关联的回调函数放入回调队列中
- 调用栈中任务执行完毕后,此时处于空闲状态,会从回调队列中获取任务进行处理
以上任务会不断重复,这就是JavaScript的运行机制,称为事件循环机制
NodeJS中的Event Loop
Event Loop的设计是Node.js可以通过将操作转移到系统内核中,来执行非阻塞I/O操作
与浏览器不同,事件循环分成不同的阶段
由于事件循环的划分不一致,nodejs在对宏任务和微任务上的处理也不一样
为什么会将异步任务分为宏任务和微任务?是为了避免回调队列中等待异步执行的任务过多,导致某些异步任务的等待时间过长
在浏览器的异步回调队列中,宏任务和微任务的执行过程如下:
参考教程:前端进阶教程