JavaScript 事件循环及异步原理(完全指北)
- 作者: 五速梦信息网
- 时间: 2026年04月04日 13:37
引言
JS单线程
- JS 是单线程的,只有一个主线程
- 函数内的代码从上到下顺序执行,遇到被调用的函数先进入被调用函数执行,待完成后继续执行
- 遇到异步事件,浏览器另开一个线程,主线程继续执行,待结果返回后,执行回调函数
JS浏览器环境nodeJs环境
NodeNode.jslibuvNodelibuv
微任务宏任务JS
为什么 是单线程的
JavaScriptJavaScriptDOM
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
HTML5CPUJavaScriptDOMJavaScript
函数调用栈与任务队列
函数调用栈
JavaScriptcall stack
这类似于一个乒乓球桶,第一个放进去的乒乓球会最后一个拿出来。
举个栗子:
function a() {
console.log(“I‘m a!”);
};
function b() {
a();
console.log(“I’m b!”);
};
b();
执行过程如下所示:
main.jsmain.jsb()b()b()main.jsb()a()a()a()b()main.jsa()I‘m a!a()b()main.jsb()I’m b!b()main.jsmain.jsb()
这就是一个简单的调用栈,在调用栈中,前一个函数在执行的时候,下面的函数全部需要等待前一个任务执行完毕,才能执行。
JavaScript任务队列
任务队列
同步任务(synchronous)异步任务(asynchronous)
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
“任务队列”(task queue)“任务队列”
setTimeoutsetTimeout
当然,一般不同的异步任务的回调函数会放入不同的任务队列之中。等到调用栈中所有任务执行完毕之后,接着去执行任务队列之中的回调函数。
用一张图来表示就是:

ChromewebcorewebcoreDOM Bindingnetworktimer
我们先来看一个有意思的现象,我运行一段代码,大家觉得输出的顺序是什么:
setTimeout(() => {
console.log('setTimeout')<br/>
}, 22)
for (let i = 0; i++ < 2;) {
i === 1 && console.log('1')<br/>
}
setTimeout(() => {
console.log('set2')<br/>
}, 20)
for (let i = 0; i++ < 100000000;) {
i === 99999999 && console.log('2')<br/>
}
没错!结果很量子化:

那么这实际上是一个什么过程呢?那我就拿上面的一个过程解析一下:
setTimeoutsetTimeoutWebapisAPItimer

setTimeout异步APIapp.js
setTimeout
- 调用栈顺序调用任务
- 当调用栈发现异步任务时,将异步任务交给其他模块处理,自己继续进行下面的调用
- 异步执行完毕,异步模块将任务推入任务队列,并通知调用栈
- 调用栈在执行完当前任务后,将执行任务队列里的任务
- 调用栈执行完任务队列里的任务之后,继续执行其他任务
事件循环(Event Loop)
那么,了解了这么多,小伙伴们能从事件循环上面来解析下面代码的输出吗?
for (var i = 0; i < 10; i++) {
setTimeout(() => {<br/>
console.log(i)<br/>
}, 1000)<br/>
}
console.log(i)
解析:
varisetTimeoutconsole.log(i)ii1010i1010
用下图示意:

现在小伙伴们是否已经恍然大悟,从底层了解了为什么这个代码会输出这个内容吧:

那么问题又来了,我们看下面的代码:
setTimeout(() => {
console.log(4)<br/>
}, 0);
new Promise((resolve) =>{
console.log(1);<br/>
for (var i = 0; i < 10000000; i++) {<br/>
i === 9999999 && resolve();<br/>
}<br/>
console.log(2);<br/>
}).then(() => {
console.log(5);<br/>
});
console.log(3);
大家觉得这个输出是多少呢?
promise123PromisesetTimeoutsetTimeoutPromise

1,2,3,5,4
宏任务和微任务
什么是宏任务和微任务
macro-task(宏任务)micro-task(微任务)taskjobs
macro-task(宏任务)script(整体代码)setTimeoutsetIntervalsetImmediate(NodeJs)I/OUI renderingmicro-task(微任务)process.nextTick(NodeJs)PromiseObject.observe(已废弃)MutationObserver(html5新特性)setTimeoutsetInterval
micro-task(微任务)micro-task(微任务)macro-task(宏任务)macro-task(宏任务)setTimeoutmicro-task(微任务)
解析
现在我就开始解析上面的代码。
scriptsetTimeoutPromisePromisePromisenew.thenmicro-taskPromiseapp.js 3app.jsmacro-task(宏任务)scriptmicro-task(微任务)micro-task(微任务)macro-task(宏任务)setTimeoutmacro-task(宏任务)setTimeout1,2,3,5,4
那么上面这个例子的输出结果就显而易见。大家可以自行尝试体会。
总结
macro-taskmicro-taskmicro-taskmacro-taskmacro-task(宏任务)script(整体代码)setTimeoutsetIntervalsetImmediate(NodeJs)I/OUI renderingmicro-task(微任务)process.nextTick(NodeJs)PromiseObject.observe(已废弃)MutationObserver(html5新特性)
进阶举例
那么,我再来一些有意思一点的代码:
<script>
setTimeout(() => {
console.log(4)<br/>
}, 0);
new Promise((resolve) => {
console.log(1);<br/>
for (var i = 0; i < 10000000; i++) {<br/>
i === 9999999 && resolve();<br/>
}<br/>
console.log(2);<br/>
}).then(() => {
console.log(5);<br/>
});
console.log(3);
</script>
<script>
console.log(6)
new Promise((resolve) => {
resolve()<br/>
}).then(() => {
console.log(7);<br/>
});
</script>
这一段代码输出的顺序是什么呢?
其实,看明白上面流程的同学应该知道整个流程,为了防止一些同学不明白,我再简单分析一下:
script1scriptscript1script2script1script1script2
1,2,3,5,6,7,4
了解了上面的内容,我觉得再复杂一点异步调用关系你也能搞定:
setImmediate(() => {
console.log(1);<br/>
},0);
setTimeout(() => {
console.log(2);<br/>
},0);
new Promise((resolve) => {
console.log(3);<br/>
resolve();<br/>
console.log(4);<br/>
}).then(() => {
console.log(5);<br/>
});
console.log(6);
process.nextTick(()=> {
console.log(7);<br/>
});
console.log(8);
//输出结果是3 4 6 8 7 5 2 1

setTimeout(() => {
console.log('to1');<br/>
process.nextTick(() => {<br/>
console.log('to1_nT');<br/>
})<br/>
new Promise((resolve) => {<br/>
console.log('to1_p');<br/>
setTimeout(() => {<br/>
console.log('to1_p_to')<br/>
})<br/>
resolve();<br/>
}).then(() => {<br/>
console.log('to1_then')<br/>
})<br/>
})
setImmediate(() => {
console.log('imm1');<br/>
process.nextTick(() => {<br/>
console.log('imm1_nT');<br/>
})<br/>
new Promise((resolve) => {<br/>
console.log('imm1_p');<br/>
resolve();<br/>
}).then(() => {<br/>
console.log('imm1_then')<br/>
})<br/>
})
process.nextTick(() => {
console.log('nT1');<br/>
})
new Promise((resolve) => {
console.log('p1');<br/>
resolve();<br/>
}).then(() => {
console.log('then1')<br/>
})
setTimeout(() => {
console.log('to2');<br/>
process.nextTick(() => {<br/>
console.log('to2_nT');<br/>
})<br/>
new Promise((resolve) => {<br/>
console.log('to2_p');<br/>
resolve();<br/>
}).then(() => {<br/>
console.log('to2_then')<br/>
})<br/>
})
process.nextTick(() => {
console.log('nT2');<br/>
})
new Promise((resolve) => {
console.log('p2');<br/>
resolve();<br/>
}).then(() => {
console.log('then2')<br/>
})
setImmediate(() => {
console.log('imm2');<br/>
process.nextTick(() => {<br/>
console.log('imm2_nT');<br/>
})<br/>
new Promise((resolve) => {<br/>
console.log('imm2_p');<br/>
resolve();<br/>
}).then(() => {<br/>
console.log('imm2_then')<br/>
})<br/>
})
// 输出结果是:?
大家可以在评论里留言结果哟~
- 上一篇: javascript 之执行环境
- 下一篇: JavaScript 事件循环
相关文章
-
javascript 之执行环境
javascript 之执行环境
- 互联网
- 2026年04月04日
-
JavaScript 中 4 种常见的内存泄露陷阱
JavaScript 中 4 种常见的内存泄露陷阱
- 互联网
- 2026年04月04日
-
javascript 中的location.href 并不是立即执行的,是在所在function 执行完之后执行的。
javascript 中的location.href 并不是立即执行的,是在所在function 执行完之后执行的。
- 互联网
- 2026年04月04日
-
JavaScript 事件循环
JavaScript 事件循环
- 互联网
- 2026年04月04日
-
JavaScript 富文本编辑器
JavaScript 富文本编辑器
- 互联网
- 2026年04月04日
-
JavaScript 对象与数组参考大全
JavaScript 对象与数组参考大全
- 互联网
- 2026年04月04日






