我们都知道JavaScript是单线程的,也就是说同一时间只能干一件事。这是因为JavaScript主要是用来操作DOM的,如果变成多线程,浏览器就不知道该听谁的了。不过虽然js是单线程,但是我们可以通过Event Loop模拟多线程,实现主线程的不阻塞。
如图所示,js中的内存分为 堆内存(heap) 和 栈内存(stack), 堆内存 中存的是我们声明的object类型的数据,栈内存 中存的是 基本数据类型 以及 函数执行时的运行空间。我们的 同步 代码就放在 执行栈 中,浏览器会将 dom事件 ajax setTimeout等异步代码放到队列中,等待执行栈中的代码都执行完毕,才会执行队列中的代码。
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
console.log(3);
根据之前说的,setTimeout 会被放到队列中,等待执行栈中的代码执行完毕才会执行,所以会输出1, 3, 2
————————————————————————————————
但是异步代码也是有区别的:
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
Promise.resolve().then(() => {
console.log(3)
})
输出的永远是1, 3, 2, 也就是说 promise 在 setTimeout 之前执行了。这是因为 异步任务 分为 微任务(microtask) 和 宏任务(task),执行的顺序是 执行栈中的代码 => 微任务 => 宏任务。
————————————————————————————————
执行栈
- 执行栈中的代码永远最先执行
微任务(microtask):
Promise.then、 MutaionObserver、process.nextTick (Node.js)
- 当执行栈中的代码执行完毕,会在执行宏任务队列之前先看看微任务队列中有没有任务,如果有会先将微任务队列中的任务清空才会去执行宏任务队列
宏任务(task):
script(整体代码)、setTimeout、setInterval、I/O、事件、postMessage、 MessageChannel、setImmediate (Node.js)
- 等待执行栈和微任务队列都执行完毕才会执行,并且在执行完每一个宏任务之后,会去看看微任务队列有没有新添加的任务,如果有,会先将微任务队列中的任务清空,才会继续执行下一个宏任务
setTimeout(() => {
console.log('timeout1')
Promise.resolve().then(() => {
console.log('promise1')
})
Promise.resolve().then(() => {
console.log('promise2')
})
}, 100)
setTimeout(() => {
console.log('timeout2')
Promise.resolve().then(() => {
console.log('promise3')
})
}, 200)
- 先将两个setTimeout塞到宏任务队列中
- 当第一个setTimeout1时间到了执行的时候,首先打印timeout1,然后在微任务队列中塞入promise1和promise2
- 当第一个setTimeout1执行完毕后,会去微任务队列检查是不是空的,他发现了有两个promise,会把两个promise按顺序执行完再去执行下一个宏任务
- 两个promise执行完毕后会微任务队列中没有任务了,会去宏任务中执行下一个任务 setTimeout2
- 当setTimeout2 执行的时候,先打印一个timeout2,然后又在微任务队列中塞了一个promise3
- 当setTimeout2执行完毕后会去微任务队列检查,发现有一个promise3,会将promise3执行
- 会依次打印 timeout1 promise1 promise2 timeout2 promise3