事件循环理解

事件循环机制

js是一种单线程语言,而IO操作耗时较长,单线程模式下如果采用同步的方式会导致程序停滞,因此采用了异步的模式。异步的实现主要是靠事件模式进行通知,事件执行的过程就是事件循环。

事件循环的基本模型

下面是事件循环的基本原理(取自阮一峰的博客

我们知道,函数执行的过程是将函数执行上下文压入栈中,直到栈中清空,表示这个任务执行结束。为了保证异步的函数按顺序执行,出现了task queue(任务队列)。任务队列的作用是将各种事件或异步的操作进行通知时加入其回调函数。js引擎会不停的从任务队列中取出任务,压入到栈中执行,执行完成后再取下一个任务执行,如此一直到任务队列为空。如果为空,会一直等待新的任务出现。这种不停的等待处理事件的循环称之为事件循环。

事件循环的细节

由于执行的优先级不同,产生异步的类型也是分为两类: macro-task和micro-task。

macro-tas:scripting(整体代码), setTimeout,setInterval,setImmediate,I/O,UI rendering

micro-task:Promise, process.nextTick, mutationObserver

因此,事件循环实际上是分成两个小循环的。macro-task就是我们基本模型的中task-queue,而在macro-task执行过程中会生成新的micro-task,这些micro-task会在当前栈内任务执行完成后立即执行,清空micro-task后才会回到task-queue中继续下一个任务。示例如下

每当产生一个macro-task时,会加入到任务队列中。整体的事件循环会不停的从任务队列中取出任务执行。如果执行过程中生成了micro-task,则加入micro-task的队列中。当前的macro-task执行完成后,会立即执行micro-task中的任务,直到Micro-task中任务全部清空后,会继续执行事件循环。

事件循环的各种事件优先级

对于macro-task来讲,所有的任务是根据添加的先后顺序决定的。比如我先执行的setTimeout,那么setTimeout在到达时间后会直接添加至任务队列,那么setTimeout会先执行。对于setTimeout, setInterval, setImmediate这种计时器类型的函数,系统会将相同的任务归并,即两条setTimeout会同时执行,不管添加的顺序是怎么样的。

对于micro-task来讲,顺序基本是固定的,process.nextTick总会优先于Promise执行。
看下面的例子:

// demo02
console.log('golb1');

setTimeout(function() {
    console.log('timeout1');
    process.nextTick(function() {
        console.log('timeout1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout1_promise');
        resolve();
    }).then(function() {
        console.log('timeout1_then')
    })
})

setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})

process.nextTick(function() {
    console.log('glob1_nextTick');
})
new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})

setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})

process.nextTick(function() {
    console.log('glob2_nextTick');
})
new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})

setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})

输出结果:

golb1
glob1_promise
glob2_promise
glob1_nextTick
glob2_nextTick
glob1_then
glob2_then
timeout1
timeout1_promise
timeout2
timeout2_promise
timeout1_nextTick
timeout2_nextTick
timeout1_then
timeout2_then
immediate1
immediate1_promise
immediate2
immediate2_promise
immediate1_nextTick
immediate2_nextTick
immediate1_then
immediate2_then

即使改变了process.nextTick和Promise的顺序,也会发现是process.nextTick先执行。具体分析可参见原文

总结

事件循环本质上是比较复杂的,在某此引擎上实现可能有细微的差别,了解基本原理后再解决问题就很方便了

posted @ 2017-12-19 12:00  无梦灬  阅读(772)  评论(0)    收藏  举报