0. 结论
从现象上来看,setTimeout是异步的,但原理并不是多线程,而是任务队列的轮询机制,且受主线程阻塞
1. 先看例子
先看以下两种情况的输出:
例一:
sleep(3000);
console.log("start!");
setTimeout(() => {
console.log(222);
}, 3000)
setTimeout(() => {
console.log(444);
}, 5000)
setTimeout(() => {
console.log(666);
}, 1000)
结果如下
start! // 3s
666 // 4s
222 // 6s
444 // 3+5=8s
例二:
console.log(111)
setTimeout(() => {
console.log(222);
}, 3000)
console.log(333)
setTimeout(() => {
console.log(444);
}, 0)
console.log(555)
setTimeout(() => {
console.log(666);
}, 1000)
结果如下
111 // 立即
333 // 立即
555 // 立即
444 // 0s
666 // 1s
222 // 3s
到这里可以说明一点,setTimeout没有阻塞主线程,而且只有执行到setTimeout函数时计时器才开始倒计时,这其实是由于浏览器采用了任务队列的机制
2. 任务队列
在JavaScript中 所有的任务可以分为两种 一种是同步任务(synchronous)另一种是异步任务(asynchronous)
同步任务指: 在主线程上排队执行的任务 只有前一个任务执行完毕后 才能执行下一个任务
异步任务指: 不进入主线程 而进入 任务队列(task queue)的任务 等主线程的任务全部执行完成后 主线程会通过event loop(事件循环) 去询问任务队列中是否有可以被执行的任务了 如果有可以被执行的任务 这个时候这个任务就会被放进 主线程执行
下面这张图就更详细的解释了主线程和任务队列的关系
至此 setTimeout并不是异步的 而是JavaScript在执行的时候 会将setTimeout放入任务队列 等待主线程的执行(不阻塞主线程)全部执行完成后 再通过event loop去询问任务队列中是否有可执行的代码 再继续放入主线程中执行 故产生了异步的假象。