javascript 事件循环
可以先来看这样的一段代码, 可以思考下会输出什么结果:
console.log('event start')
setTimeout(function () {
console.log('setTimeout');
});
new Promise(function(resolve,reject){
console.log('promise start')
resolve()
}).then(function(){
console.log('promise end')
})
console.log('event end')
正确的输出结果如下:
那么, 为什么会得到这样的结果呢? 这就和 javascript
的执行机制密切相关了.
事件队列和事件循环
javascript
是一门单线程的语言, 这就意味着在执行代码的时候, 都只有一个主线程来处理所有的任务.
我们都知道 javascript
包括同步代码和异步代码, 那么 javascript
是怎么处理这两种情况的呢?
可以通过下图简单说明:
- 同步和异步任务分别进入不同的执行
场所
, 同步的进入主线程,异步的进入 Event Table
并注册函数
- 当指定的事情完成时,
Event Table
会将这个函数(回调函数)移入 Event Queue
- 主线程内的任务执行完毕为空, 会去
Event Queue
读取对应的函数,进入主线程执行
- 上述过程会不断重复, 也就是常说的
Event Loop(事件循环)
这里我们引进了 Event Queue
事件队列这一概念. 所有异步操作的回调都会进入到这里. 然后等到主线程空闲, 就会从这里调取回调执行.
可以通过上图中了解 event loop
, 事件队列以及主线程之间的关系.
setTimeout
setTimeout
相信大家都有使用过, 可以延时执行并且是异步执行的.
但是有时候我们得到的结果往往是代码实际执行的时间比我们想要延时执行的时间要久。这又是为什么呢?
这就和我们之前所说的 Event Loop
有关了, 我们可以来具体看下 setTimeout
的执行步骤:
setTimeout(function () {
asyncFn()
}, 1000);
syncFn()
asyncFn
将异步执行函数放在 Event Table
, 并且开始计时
- 开始执行
syncFn
, 但是 syncFn
可能需要处理的内容很多, 执行时间超过 1
秒, 但是计时还在继续
- 计时到达
1
秒, setTimeout
延时完成, asyncFn
进入 Event Queue
事件队列, 但是主线程还在执行, 所以只能等待
syncFn
执行完成, 此时 asyncFn
从事件队列中进入主线程执行
所以有时候会出现代码实际执行时间比延时时间长的情况.
macro-task 和 micro-task
以上我们只是简单的分为了同步任务和异步任务. 其实我们还有更加具体的定义:
macro-task
宏任务: setTimeout
, setInterval
micro-task
微任务: Promise
之前我们说过异步任务会进入到事件队列中, 不同类型的任务会进入到不同的队列中, 比如宏任务会进入到宏任务队列中, 微任务会进入到微任务队列中.
我们可以参考下图来理解事件循环, 宏任务, 微任务的关系:
我们只要记住 当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行
这时候我们就可以解释一开始的代码执行结果了:
console.log('event start')
setTimeout(function () {
console.log('setTimeout');
});
new Promise(function(resolve,reject){
console.log('promise start')
resolve()
}).then(function(){
console.log('promise end')
})
console.log('event end')
- 主线程执行按顺序代码
- 遇到
setTimeout
, 回调进入到宏任务队列上
- 遇到
Promise
, 立即执行, then
函数进入到微任务队列
- 同步代码执行结束, 主线程检查是否存在微任务, 发现
then
, 执行
- 微任务执行完毕, 再去查找宏任务
setTimeout
, 执行
setTimeout
执行结束, 检查是否存在微任务, 不存在, 结束.
参考文献
javascript 事件循环
可以先来看这样的一段代码, 可以思考下会输出什么结果:
console.log('event start') setTimeout(function () { console.log('setTimeout'); }); new Promise(function(resolve,reject){ console.log('promise start') resolve() }).then(function(){ console.log('promise end') }) console.log('event end')
正确的输出结果如下:
// event start // promise start // event end // promise end // setTimeout
那么, 为什么会得到这样的结果呢? 这就和
javascript
的执行机制密切相关了.事件队列和事件循环
javascript
是一门单线程的语言, 这就意味着在执行代码的时候, 都只有一个主线程来处理所有的任务.我们都知道
javascript
包括同步代码和异步代码, 那么javascript
是怎么处理这两种情况的呢?可以通过下图简单说明:
场所
, 同步的进入主线程,异步的进入Event Table
并注册函数Event Table
会将这个函数(回调函数)移入Event Queue
Event Queue
读取对应的函数,进入主线程执行Event Loop(事件循环)
这里我们引进了
Event Queue
事件队列这一概念. 所有异步操作的回调都会进入到这里. 然后等到主线程空闲, 就会从这里调取回调执行.可以通过上图中了解
event loop
, 事件队列以及主线程之间的关系.setTimeout
setTimeout
相信大家都有使用过, 可以延时执行并且是异步执行的.但是有时候我们得到的结果往往是代码实际执行的时间比我们想要延时执行的时间要久。这又是为什么呢?
这就和我们之前所说的
Event Loop
有关了, 我们可以来具体看下setTimeout
的执行步骤:setTimeout(function () { asyncFn() }, 1000); syncFn()
asyncFn
将异步执行函数放在Event Table
, 并且开始计时syncFn
, 但是syncFn
可能需要处理的内容很多, 执行时间超过1
秒, 但是计时还在继续1
秒,setTimeout
延时完成,asyncFn
进入Event Queue
事件队列, 但是主线程还在执行, 所以只能等待syncFn
执行完成, 此时asyncFn
从事件队列中进入主线程执行所以有时候会出现代码实际执行时间比延时时间长的情况.
macro-task 和 micro-task
以上我们只是简单的分为了同步任务和异步任务. 其实我们还有更加具体的定义:
macro-task
宏任务:setTimeout
,setInterval
micro-task
微任务:Promise
之前我们说过异步任务会进入到事件队列中, 不同类型的任务会进入到不同的队列中, 比如宏任务会进入到宏任务队列中, 微任务会进入到微任务队列中.
我们可以参考下图来理解事件循环, 宏任务, 微任务的关系:
我们只要记住 当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行
这时候我们就可以解释一开始的代码执行结果了:
console.log('event start') setTimeout(function () { console.log('setTimeout'); }); new Promise(function(resolve,reject){ console.log('promise start') resolve() }).then(function(){ console.log('promise end') }) console.log('event end')
setTimeout
, 回调进入到宏任务队列上Promise
, 立即执行,then
函数进入到微任务队列then
, 执行setTimeout
, 执行setTimeout
执行结束, 检查是否存在微任务, 不存在, 结束.参考文献