美文网首页
图解搞懂JavaScript引擎Event Loop

图解搞懂JavaScript引擎Event Loop

作者: 奉献_97a8 | 来源:发表于2018-01-20 17:29 被阅读0次

[图片上传失败...(image-c3e3cb-1516440392143)]

一,js单线程存在的问题

js是单线程的,处理任务是一件接着一件处理,所以如果一个任务需要处理很久的话,后面的任务就会被阻塞

所以js通过Event Loop事件循环的方式解决了这个问题,在了解事件循环前,我们需要了解一些关键词

二,什么是stack,queue,heap,event loop

  • stack(栈):吃多了吐
  • queue(队列):吃多了...释放
  • heap(堆):存储obj对象

执行栈

[图片上传失败...(image-252451-1516440392143)]

js引擎运行时,当代码开始运行的时候,会将代码,压入执行栈进行执行

例:
[图片上传失败...(image-e1a3e3-1516440392143)]

当代码被解析后,函数会依次被压入到栈中

[图片上传失败...(image-fd2bff-1516440392143)]

有入栈,就要有出栈,当函数c执行完,开始出栈

[图片上传失败...(image-319c57-1516440392143)]

当执行栈遇到异步

前面执行栈,先入后出,但其实也是同步的,同步就意味着会阻塞,所以需要异步,那当执行栈中出现异步代码会怎么样

[图片上传失败...(image-49169d-1516440392143)]

此时在代码中,添加点击事件和setTimeout,现在观察一下执行顺序

[图片上传失败...(image-8ba8f-1516440392143)]

观察此时的执行栈效果,和上面的函数嵌套有显著区别

1,console.log("sync")的语句,不会被压入到执行栈底部,因为console已经执行结束了

2,click和settimeout都入栈了,但它们内部的console没有入栈的,这说明他们没有执行完

3,如果click没有执行完,那为什么setTimeout会入栈,不应该被阻塞吗?

答案是:当浏览器在执行栈执行的时候,发现有异步任务之后,会交给webapi去维护,而执行栈则继续执行后面的任务

[图片上传失败...(image-12e78f-1516440392143)]

同样,setTimeout同样会被添加到webapi中

[图片上传失败...(image-ef10b2-1516440392143)]

webapi是浏览器自己实现的功能,这里专门维护事件。

上面setTimeout旁边有个进度条,这个进度就是设置的等待时间

回调队列callback queue

上面的例子,当setTimeout执行结束的时候,是不是就应该回到执行栈,进行执行输出呢?

答案:并不是!

[图片上传失败...(image-4f8ba-1516440392143)]

此时,倒计时结束后的setTimeout的可执行函数,被放如了回调队列

最后,setTimeout的可执行函数,被从回调队列中取出,再此放入了执行栈

[图片上传失败...(image-47b36b-1516440392143)]

这样的执行过程就叫 event loop事件循环

Event Loop的具体流程

执行栈任务清空后,才会从回调队列头部取出一个任务

[图片上传失败...(image-1fb582-1516440392143)]

上面是一个最简单的例子,输出结果是1,3,2

这是为什么?

[图片上传失败...(image-d98bb7-1516440392143)]

上图展示了具体的执行顺序:

1,console.log(1)被压入执行栈

2,setTimeout在执行栈被识别为异步任务,放入webapis中

3,console.log(3)被压入执行栈,此时setTimeout的可执行代码还在回调队列里等待

4,console.log(3)执行完成后,从回调队列头部取出console.log(2),放入执行栈

5,console.log(2)执行

回调队列先进先出

需要格外注意,回调队列是先进先出的,例:
[图片上传失败...(image-688aee-1516440392143)]

[图片上传失败...(image-b475c7-1516440392143)]

当console.log(4)执行完成后,从回调队列里取出了console.log(2);

注意:只有console.log(2)执行完成,执行栈再次清空时,才会从回调队列取出console.log(3)

测试概念是否正确

[图片上传失败...(image-aa146a-1516440392143)]

上面的代码最后输出1,5,2,4,3,执行过程:

1,输出1,将2push进回调队列

2,将4push进回调队列

3,输出5

4,清空了执行栈,读取输出2,发现有3,将3push进回调队列

5,清空了执行栈,读取输出4

6,清空了执行栈,读取输出3

至此,看起来好像没问题了,但是!!!!!!,事情还没有结束

Macrotask(宏任务)、Microtask(微任务)

通过上面的例子,想必已经对event loop有了一定的了解,现在继续看一个例子

console.log(1);
setTimeout(()=>{
    console.log(2)
})
var p = new Promise((resolve,reject)=>{
    console.log(3)
    resolve("成功")
})
p.then(()=>{
    console.log(4)
})
console.log(5)

按照event loop的概念,应该是1,3,5,2,4,因为setTimeout和then会被放到回调队列里,然后又是先进先出,所以应该是2先输出,4后输出

但事实输出的顺序是1,3,5,4,2!

[图片上传失败...(image-9fefe9-1516440392143)]

这是因为promise的then方法,被认为是在Microtask微任务队列当中

什么是Macrotask(宏任务)

Macrotask(宏任务)很好理解,就是咱们前面介绍过的回调队列callback queue

什么是Microtask(微任务)

Microtask(微任务)同样是一个任务队列,这个队列的执行顺序是在清空执行栈之后

用图展示就是

[图片上传失败...(image-99e831-1516440392143)]

可以看到Macrotask(宏任务)也就是回调队列上面还有一个Microtask(微任务)

Microtask(微任务)虽然是队列,但并不是一个一个放入执行栈,而是当执行栈请空,会执行全部Microtask(微任务)队列中的任务,最后才是取回调队列的第一个Macrotask(宏任务)

例:

[图片上传失败...(image-5e26e-1516440392143)]

上面的执行过程是:

1,将setTimeout给push进宏任务

2,将then(2)push进微任务

3,将then(4)push进微任务

4,任务队列为空,取出微任务第一个then(2)压入执行栈

5,输出2,将then(3)push进微任务

6,任务队列为空,取出微任务第一个then(4)压入执行栈

7,输出4

8,任务队列为空,取出微任务第一个then(3)压入执行栈

9,输出3

10,任务队列为空,微任务也为空,取出宏任务中的setTimeout(1)

11,输出1

为什么then是微任务

这和每个浏览器有关,每个浏览器实现的promise不同,有的then是宏任务,有的是微任务,chrome是微任务,普遍都默认为微任务

除了then以外,还有几个事件也被记为微任务:

  • process.nextTick
  • promises
  • Object.observe
  • MutationObserver
console.log("start");
setImmediate(()=>{
    console.log(1)
})
Promise.resolve().then(()=>{
    console.log(4);
})
Promise.resolve().then(()=>{
    console.log(5);
})
process.nextTick(function foo() {
    console.log(2);
});
process.nextTick(function foo() {
    console.log(3);
});
console.log("end")

上面代码输出start,end,2,3,4,5,1

process.nextTick的概念和then不太一样,process.nextTick是加入到执行栈底部,所以和其他的表现并不一致

最后的测试

console.log("1");
setTimeout(()=>{
    console.log(2)
    Promise.resolve().then(()=>{
        console.log(3);
        process.nextTick(function foo() {
            console.log(4);
        });
    })
})
Promise.resolve().then(()=>{
    console.log(5);    
    setTimeout(()=>{
        console.log(6)
    })
    Promise.resolve().then(()=>{
        console.log(7);
    })
})

process.nextTick(function foo() {
    console.log(8);
    process.nextTick(function foo() {
        console.log(9);
    });
});
console.log("10")

执行顺序:

1,输出1

2,将setTimeout(2)push进宏任务

3,将then(5)push进微任务

4,在执行栈底部添加nextTick(8)

5,输出10

6,执行nextTick(8)

7,输出8

8,在执行栈底部添加nextTick(9)

9,输出9

10,执行微任务then(5)

11,输出5

12,将setTimeout(6)push进宏任务

13,将then(7)push进微任务

14,执行微任务then(7)

15,输出7

16,取出setTimeout(2)

17,输出2

18,将then(3)push进微任务

19,执行微任务then(3)

20,输出3

21,在执行栈底部添加nextTick(4)

22,输出4

23,取出setTimeout(6)

24,输出6

最后结果是:1,10,8,9,5,7,2,3,4,6

参考

相关文章

网友评论

      本文标题:图解搞懂JavaScript引擎Event Loop

      本文链接:https://www.haomeiwen.com/subject/zjvtaxtx.html