Javascript是单线程,非阻塞的
单线程
javascript的主要作用是操作DOM,如果js为多线程的话,会非常复杂。比如,如果两个线程同时操作了同一个DOM,那么,DOM如何渲染,就是一个问题。
基于此,js 是单线程的。
非阻塞
js的非阻塞是由事件循环(Event loop)实现的
浏览器的事件循环
执行栈和任务队列
当我们调用一个方法时,js会生成一个与这个方法对应的执行环境,也称作执行上下文。 这个执行环境中存储这个方法的私有作用域、上层作用域的指向、方法的参数、这个作用域中定义的变量以及这个作用域的this对象。当一系列方法被调用时,因为js是单线程的,同一时间只可以执行一个方法。于是这些方法被排列在一个单独的地方。
这个地方就是执行栈。
执行栈
每调用一个函数,解释器就会将其加入栈中,并开始执行 正在执行的函数中,如果调用了另一个函数,则会将调用的函数,加入栈中,并立即执行 当前函数如果执行完毕,解释器会将其清出执行栈,继续执行当前执行环境中剩余的代码 如果分配的空间被占满,则会出现“堆栈溢出”的报错
栈的特点是先进后出,后进先出
任务队列
在执行代码的过程之中,如果出现了一些异步代码(如setTimeout,promise等),此时,浏览器会将这些代码放在一个线程之中(幕后线程)去等待,不会阻塞主线程的执行,主线程继续执行剩余的代码。如果幕后线程的任务准备好了(setTimeout时间到了,异步请求返回结果了),浏览器会将任务放进任务队列之中。当主线程执行完所有的代码之后,会遍历任务队列的任务,依次执行,直到队列中的任务全部完成。
队列的特点是先进先出,后进后出
宏任务和微任务
如果当前任务队列中,有多个任务,那么执行任务的顺序是?
任务,分为两种,宏任务(Macrotask),微任务(Microtask)
| 宏任务(Macrotask) | 微任务(Microtask) |
|---|---|
| setTimeout | requestAnimationFrame(有争议) |
| setInterval | MutationObserver(浏览器环境)) |
| MessageChannel | Promise.[then/catch/finally]中的代码,才是异步的微任务 |
| setImmediate(node环境) | queueMicrotask) |
| Script(整体代码块) | queueMicrotask) |
事件循环的过程
- 检查Macrotask队列是否为空,如果为空,则跳3,不为空,则执行下一步
- 从Macrotask中取出队首的任务去执行栈中执行(仅执行一个),执行完毕后,进行下一步
- 检查Microtask队列是否为空,如果为空,则跳1(进入新的事件循环),否则,执行下一步
- 从Microtask队列中取出队首的任务去执行栈中执行,执行完毕后,跳3(注,如果在微任务执行过程中,产生了新的微任务,此时直接添加到Microtask队列之中)
为什么要有两种类型的任务
页面渲染事件,各种IO的完成事件等随时被添加到任务队列中,一直会保持先进先出的原则执行,我们不能准确地控制这些事件被添加到任务队列中的位置。但是这个时候突然有高优先级的任务需要尽快执行,那么一种类型的任务就不合适了,所以引入了微任务队列。
参考文章: