引言
我们先来看一段代码
console.log(1)
setTimeout(() => {
console.log(3)
}, 2000)
console.log(2)
这段代码我们一下就可以看出肯定是先打印1和2再打印3,这是因为打印三需要等待2秒。那我们再来看一下下面这段代码:
console.log(1)
setTimeout(() => {
console.log(3)
}, 0)
console.log(2)
看到这段代码我们就会有疑惑,有定时器但是定时器的等待时间为0这是先打印2还是先打印3呢?JavaScript从上到下执行,那么遇到0s的计时器函数,就应该先输出3才对啊。这就是因为后面要提到的JavaScript执行机制导致的啦,因为setTimeout是异步任务。我i们运行一下就会知道这段代码会先打印2,这是为什么呢?这就涉及到了JavaScript程序的执行机制,接下俩我们一起看看吧!
JavaScript是单线程
JavaScript的核心特征就是单线程,即同一时间只能做一件事。
为什么它是单线程呢?因为JavaScript作为浏览器脚本语言,它的主要用途就是与用户互动、操作DOM。既然如此,如果它不是单线程的话,假如一个线程在DOM节点上添加内容,同时另一个线程删除这个节点。可以看出,如果JavaScript不是单线程的话,那么将会导致同步问题。
任务队列
JavaScript是单线程语言,这也就导致了如果有一个任务等待很长的时间,这个时候就会导致阻塞,程序就会“卡死”,用户体验非常差。所以JavaScript需要异步任务。
那么,为什么JavsScript明明是单线程的,为什么能异步呢?这是因为浏览器是多线程的,通过事件循环Event Loop即可实现异步。
所有任务都可以分成两种。
- 同步任务:在主线程上排队执行的任务,只有前一个任务执行完,才能执行后一个任务
- 异步任务:不进入主线程,而是进入任务队列的任务。当异步任务的触发条件满足时,异步任务才会进入任务队列,而当主线程空了,就会去任务队列中取异步任务到主线程中执行。
常见异步任务:
- JS事件
- AJAX请求
- setTimeout和setInterval
- Promise(Promise定义部分为同步任务,回调部分为异步任务)
Event Loop事件循环机制1
- 所有同步任务进入主线程,而异步任务则是进入Event Table注册回调函数
- 当异步任务的触发条件满足时,异步任务注册的回调函数将会从Event Table移入到任务队列Event Queue中
- 当主线程中的所有同步任务执行完毕后,系统就会去看看Event Queue中看看有没有回调函数,有的话就推到主线程中
- 主线程不断重复上面的步骤
宏任务和微任务
异步任务又可以进行更精细的划分为宏任务和微任务。
宏任务
setTimeout、setInterval、requestAnimationFrame
- 当宏任务队列中的任务全部都执行完之后,如果微任务队列不为空,则先执行微任务队列中的所有任务
微任务
Promise回调部分、process.nextTick
- 在上一个宏任务队列执行完毕后如果有微任务就会执行微任务队列中的所有元素
Event Loop事件循环机制2
- 首先执行script下的同步任务
- 执行过程中,如果遇到异步任务,则需要把它放到对应的任务队列中(遇到宏任务,则放到宏任务中;遇到微任务,则放到微任务队列中)
- 同步任务执行完毕,查看微任务队列
- 如果存在微任务,则将微任务队列全部执行(包括执行微任务中产生的新微任务)
- 如果不存在微任务,则查看宏任务队列,执行第一个宏任务,宏任务执行完后,又看看微任务队列是否有任务,有的话,又先全部执行完微任务队列,重复上述操作,知道宏任务队列为空。
练手
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
new Promise((resolve, reject) => {
for (let i = 3; i < 6; i++) {
console.log(i)
}
resolve()
}).then(() => {
console.log(6)
})
console.log(7)
解析:
1、首先,程序从上往下走,直接输出1,遇到 setTimeout 后,把它放到宏任务队列中
⭐此时,宏任务队列中为[setTimeout](这里用数组表示任务队列,左边代表先进入的任务队列)
2、继续往下跑,遇到 Promise ,因为Promise定义部分为同步任务,依次输出3, 4, 5,遇到 Promise.then() ,把它放到微任务队列中
⭐此时,宏任务队列为 [setTimeout]
⭐此时,微任务队列为 [Promise.then()]
3、输出7后,执行微任务队列中全部的任务,输出6, 再执行宏任务队列中的任务,输出2