我们知道线程是操作系统能够进行运算调度的最小单位。是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。这就意味着多线程可以同时执行多个任务,单线程同一时刻只能执行一个任务。
那为什么 JavaScript 不采用多线程这种更高效方式呢?其实这跟 JavaScript 的应用场景有关,众所周知 JavaScript 是浏览器脚本语言,主要用来处理用户的交互逻辑和 DOM,如果 JavaScript 是多线程的,同一时刻,一个线程删除 DOM 的内容,另一个线程新增 DOM 的内容,那现在到底是新增还是删除呢?因此 JavaScript 只能是单线程的,同一时刻只能做一件事。
单线程同一时刻只能做一件事,意味着所有的 JavaScript 事件执行的时候都需要排队,后边的事件只能等前边的事件执行完后才能执行。如果当前正在执行的需要耗费很多时间,后边的事件就只能等着,这就是 JavaScript 的阻塞问题。例如访问一个网站时,文字加载很快,图片加载很慢,但由于单线程,我们必须等图片加载完成后才能加载后边的文字,无疑这样的体验很差。
为了解决 JavaScript 的阻塞问题,提出了将同步任务与异步任务的概念,异步任务主要是处理耗时长的操作。这样就不会阻塞 JavaScript 的主线程。
在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
如下代码所示,我们分析一下执行流程:
console.log(0)
是同步任务,主线程执行输出 0;console.log(1)
是同步任务,主线程执行输出 1;<script>
// synchronization
console.log(0);
console.log(1);
console.log(2);
console.log(3);
</script>
不进入主线程直接执行,而进任务表格的任务,当其满足触发条件后,再将回调函数推进任务队列,当所有同步任务执行完毕,才会从任务队列取出异步任务放入主线程执行。
Event Table
Event Table 可以理解成一张 事件 -> 回调函数 对应表,它就是用来存储 JavaScript 中的异步事件 (request, setTimeout, IO等) 及其对应的回调函数的列表Event Queue
Event Queue 简单理解就是 回调函数 队列,所以它也叫 Callback Queue当 Event Table 中的事件被触发,事件对应的 回调函数 就会被 push 进这个 Event Queue,然后等待被执行
如下代码所示,我们分析一下执行流程:
console.log(0)
是同步任务,主线程执行输出 0;setTimeout
是异步任务,将其回调函数放入任务表格(Event tabel),从此刻开始200ms后将回调函数推进任务队列(Event queue);console.log(3)
是同步任务,主线程执行输出 3;console.log(1)
是同步任务,主线程执行输出 1;console.log(2)
是同步任务,主线程执行输出 2,当前函数执行完毕;注意这里的setTimeout
会在200ms后将回调函数放入任务队列等待主线程执行,而不是200ms后主线程执行,这就是有时候延时时间设置的200ms,但是真正执行时延时不只200ms的原因。
从上面的流程和输出可以看到我们将耗时的任务作为异步任务让他先在别的地方执行(后面会解释具体在哪里),这样那些不耗时的同步任务就能先执行。例如当我们访问一个网站时,那么我们就可以异步加载图片,先加载显示文字,显示图片的地方用别的元素占位,这样就极大的提高了用户体验。
// asynchronization
console.log(0);
setTimeout(() => {
console.log(1);
console.log(2);
},200)
console.log(3);
异步任务又分为宏任务和微任务,微任务的执行优先级高于宏任务。也就是说当前同步任务执行完毕,就会去判断微任务队列(microTask)是否为空,若不为空会先执行微任务队列中所有微任务,执行完毕后,取宏任务队列(macroTask)队头宏任务进入执行栈执行。
宏任务是指宿主发起的任务(浏览器/node)。
微任务是指 js 引擎发起的任务。
如下代码所示,我们分析一下执行流程:
console.log(0)
是同步任务,主线程执行输出 0;setTimeout
是异步任务,并且是宏任务,将其回调函数放入任务表格(Event tabel),从此刻开始200ms后将回调函数推进宏任务队列(macroTask);Promise.resolve(2).then
是异步任务,并且是微任务,将其回调函数放入任务表格(Event tabel),满足触发条件后将回调函数推进微任务队列(microTask);console.log(3)
是同步任务,主线程执行输出 3;console.log(1)
是同步任务,主线程执行输出 2,当前微任务队列为空。console.log(1)
是同步任务,主线程执行输出 1,当前函数执行完毕,弹栈;console.log(0);
setTimeout(() => {
console.log(1);
})
Promise.resolve(2).then((res) => console.log(res));
console.log(3);
首先要明白script
代码块是作为一个宏任务的,所以我们在执行 JavaScript 代码时是从执行宏任务开始的。当前宏任务执行完毕后,会去检查微任务队列是否为空,若不为空,会执行微任务队列中所有微任务后,再从宏任务队列取出队头宏任务执行,执行完毕后,在检查微任务队列是否为空,若为空,取宏任务队列队头宏任务执行······,如此循环往复。
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise里的定时器')
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
console.log('promise.then方法')
Promise.resolve('resolve').then((res) => console.log(res))
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
promise1 Promise {}和promise2 Promise {}
。promise.then方法执行
。promise.then方法
。resolve
。promise1 Promise {: 'success'}和promise2 Promise {: Error: error!!!
。宏任务队列和微任务队列都为空,程序运行结束。
这些问题是我在学习过程中事件循环机制时遇到的,希望能帮助到你。
从概念上宏任务是相对于微任务来说的,同步任务是相对于异步任务。
由于script
代码块是作为一个宏任务,如下所有代码就是一个宏任务,在宏任务执行过程中会判断是每一步操作是同步任务还是异步任务。也就是执行console.log(0);
时判断时同步任务,执行setTimeout
时判断时异步任务······。
<script>
// asynchronization
console.log(0);
setTimeout(() => {
console.log(1);
},200)
console.log(3);
</script>
若当前事件循环结束后进入下一轮事件循环,此时就是执行setTimeout
的回调函数。它也是一个宏任务,在执行过程中又会判断console.log(1);
是同步代码···。
() => {
console.log(1);
}
这里以浏览器环境为例。javascript 是单线程的,但是浏览器却是多线程的,如图所示 javascript 引擎线程称为主线程,它负责解析JavaScript代码;其他可以称为辅助线程。当在解析过程中遇到setTimeout
,那么就把它交给定时器线程执行,主线程继续解析下一行代码。当定时器线程执行完毕后将回调函数推进宏任务队列,然后等待主线程空闲了执行。
其实设置微任务就是给一些优先级搞的任务插队的机会。设想如果没有微任务只有宏任务,那么在执行异步任务的时候,假设遇到三个异步任务,分别执行完后会依次将回调函数添加到任务队列中等待执行,但是第三个异步任务执行完后我需要立即执行它的回调函数,这显然无法实现,只能乖乖等前两个回调函数执行完,才能执行。但是如果有微任务,他在每次执行完一个宏任务后就执行,只要将第三个异步任务设置为微任务,这个需求不就轻轻松松完成了。
因为 JavaScript 是单线程的脚本语言,所以为了解决阻塞问题,提出了将任务分为异步任务同步任务,同步任务交由主线程执行,异步任务交由辅助线程执行后将回调函数推进相应的队列中等待主线程空闲时执行。并且异步任务又分为宏任务微任务,微任务的执行优先级更高,事件循环简单来说就是宏任务微任务之间的交替执行。
非常感谢这些文章对我的帮助
我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。