初探javascript事件环EventLoop

一道javascript面试题引发了自己对eventloop的探索
先贴入这道题:
console.log('main1');
process.nextTick(function() {
    console.log('process.nextTick1');
});
setTimeout(function() {
    console.log('setTimeout');
    process.nextTick(function() {
        console.log('process.nextTick2');
    });
}, 0);
new Promise(function(resolve, reject) {
    console.log('promise');
    resolve();
}).then(function() {
    console.log('promise then');
});
console.log('main2');

在分析面试题之前我们来说一下JS的事件环,我们都知道JS是单线程的,也就是说任务需要一个接一个的按顺序执行,这是因为JS作为浏览器端的脚本语言其开始主要用途还是与用户互动和操作DOM,假如JS有两个线程,一个添加DOM一个删除DOM,这就势必会出现不可预期的后果,所以说还是单线程更适合,但是这种方式有一个弊端,就是必须要等待前一个程序执行完毕才执行下一个,所以将程序分为了两类:同步任务和异步任务。 在JS的执行栈中,同步任务进入主执行栈(也可以说主线程),而异步任务进入任务队列(TaskQueue)等待执行,任务队列可以理解成一个消息队列,I/O设备完成一件事,就向任务队列添加一个事件,一旦主执行栈中所有的同步任务执行完毕,就会读取任务队列中等待的任务,并放入执行栈开始执行,其实就是执行异步任务的回调函数,所以说异步任务必须指定回调函数,主线程会不断的循环这个动作,所以这种运行机制又称为EventLoop(事件循环)。( 这里解释一下什么叫‘任务队列’:这是一个先进先出的数据结构,排在前面的事件会优先被主线程读取,但是当有定时器的时候主线程会先检查一下执行时间。)我们画一张图来理解这个:


  从这张图中我们可以看到其中有宏任务(MacroTask)和微任务(MicroTask)之分,我们来说下这个宏任务与微任务。
    宏任务包括:
       *setImmediate
       *setTimeout
       *setInterval
    微任务包括:
       *process.nextTick
       *Promise
       *MutaionObserver
       *Object.observe(已废弃:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe)
       在单次的迭代中,event loop首先检查Macrotask队列,如果有一个Macrotask等待执行,那么执行该任务。当该任务执行完毕后(或者Macrotask队列为空),event loop继续执行Microtask队列。(V8 中 Microtask 默认是自动运行的)。
    在这里再说下堆(heap)和栈(stack),一般会划分出两种不同的内存空间堆和栈。先说一下栈吧,这是一种先进先出的数据结构,stack是有结构的,每个区块按照一定的次序存放而且可以知道其区块大小,与之配套的还有push,pop,top等方法。而一般不确定大小的数据都会放在heap(堆)里。(关于heap和stack欢迎补充)

    看完上面的想必对JS的事件循环也有了一个基本了解,下面来开始分析这道题:
    JS代码开始从上自下单线程执行:
    1.console.log('main1');进入执行栈执行
    2.遇到process.nextTick将它的回调函数先放入MicroTask(微任务)
    3.遇到setTimeout将它的回调函数放入MacroTask(宏任务队列)
    4.在执行栈中new Promise 并将.then中注册的回调放入MicroTask
    5.最后一行代码console.log('main2');会放入主执行栈执行
    综上所述,这段代码的结果就是先输出main1,然后第二步第三步我们不用管它,它不是在主执行栈中,所以直接到第四步输出promise,然后主执行栈继续执行第五步输出main2。此时主执行栈执行完毕,开始事件循环,发现在MicroTask中还有任务,开始清空微任务,第二步中我们在微任务中放入了process.nextTick所以输出process.nextTick1,在第四步中将.then的回调放入了微任务,那么微任务队列继续执行输出promise then,此时微任务队列已经清空开始事件循环宏任务队列,也就是输出第三步中的setTimeout,在输出之后发现setTimeout这个回调中还有一个process.nextTick,那么这个回调继续放入微任务队列,此时事件循环发现主执行栈中已经没有任务,那么开始执行MicroTask输出:process.nextTick2
    所以这道题的答案是://main1,promise,main2,process.nextTick1,promise then,setTimeout,process.nextTick2
    在这道题中在主进程执行完毕后,应该会调用任务队列的宏任务,但是在这之前要清空了MicorTask微任务,这与我们先说的一次事件循环的循环机制(先读取宏任务后读取微任务)相悖,所以说主进程的代码也是一个MacroTask(参考:Promises/A+规范),也就是说执行完主进程的代码后执行MicroTask这是第一个事件循环(event loop),然后又开始执行任务队列(MacroTask)和process.nextTick2属于第二个事件循环。
    下图为此题分析图:
https://img-blog.csdn.net/20180120161813298?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWFuZ2JvMTk5Mw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
    欢迎各位前端大神补充交流!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值