一、JavaScript为什么是单线程语言
1、浏览器环境的需求:JavaScript最初是为浏览器设计的,用于处理用户交互和动态内容。单线程模型避免了多个线程同时操作DOM的问题,从而减少了复杂性和潜在的错误。
2、避免竞态条件和死锁:在多线程环境中,多个线程可能会同时访问和修改相同的资源,导致竞态条件和死锁等问题。单线程模型通过强制顺序执行代码,避免了这些复杂性。
3、事件驱动的非阻塞模型:JavaScript采用事件驱动的非阻塞模型,通过事件循环来处理异步操作。这种模型允许JavaScript在单线程中高效地执行I/O操作,如网络请求、定时器和用户输入等,而不会阻塞主线程。
二、进程和线程
- 进程:进程是操作系统分配资源(如内存、文件句柄等)的基本单位。每个运行的程序至少有一个进程。
- 线程:线程是进程内的一个执行单元,也是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源。
三、宏任务和微任务
概念:在JavaScript中,宏任务(Macro Task)和微任务(Micro Task)是异步任务执行机制的一部分,用于管理任务的调度和执行顺序
为什么区分宏任务和微任务:为了优化JavaScript的异步任务调度,使得事件循环能够高效地管理和执行任务,从而提高应用程序的性能和响应性。
-
宏任务
-
<script>标签:当浏览器解析HTML文档时,遇到一个<script>标签(无论是内联的还是外部引入的),会立即停止解析文档,执行这个<script>标签内的代码,整个过程是一个宏任务,而在<script>标签内,可以创建微任务。
-
setTimeout:延时定时器任务。
-
setInterval:循环定时器任务。
-
setImmediate(
Node.js特有):当前事件循环结束后、下一个事件循环开始之前执行。 -
I/O操作:文件读取、网络请求等。
-
UI渲染:浏览器渲染引擎的渲染操作。
-
事件处理:用户交互事件(如点击、键盘事件)。
-
-
微任务
-
Promise:Promise的
then、catch和finally回调。 -
MutationObserver:监控DOM变动的回调。
-
process.nextTick(
Node.js特有):同步任务执行之后,微任务执行之前执行。 -
queueMicrotask:专门用于调度微任务的方法。
-
四、一个事件循环执行顺序
graph LR
同步任务 --> process.nextTick --> 微任务 --> DOM渲染 --> 宏任务 --> setImmediate
五、事件循环机制
1、同步代码交给js主线程,异步代码交给宿主环境。
2、同步代码放入执行栈,异步代码等待时机成熟后由宿主环境将回调推送给任务队列中排队。
3、执行栈中同步任务执行完毕,会去任务队列看是否有异步任务,异步任务分为微任务和宏任务。
4、先将所有微任务全部放入执行栈中全部执行,再取第一个宏任务放入执行栈执行。
5、重复上述过程,就构成了事件循环(Event Loop)
PS:如果宏任务执行过程中产生了微任务,这些微任务会在当前宏任务执行完毕后立即执行,而不需要等待下一次事件循环。
六、事件循环何时结束
- 没有任何待处理的任务,包括同步任务、宏任务和微任务。
- 程序退出或环境销毁
- 在Node.js中,可以使用
process.exit()显式退出事件循环和整个进程