浏览器 事件循环(Event Loop)(1)

  • 调用栈(Call stack)

调用栈就是函数执行的地方,是主线程在运行JavaScript代码的过程中形成的,遵循后进先出的规则。正在调用栈中执行的函数如果还调用了其它函数,那么这些函数也将会被添加进调用栈并执行。当函数执行完毕之后,会被移出调用栈。

来看一段代码:

function eat() {

console.log(‘吃东西啦’)

drink()

console.log(‘吃饱喝足啦’)

}

function drink() {

console.log(‘饮茶啦’)

}

eat()

这段代码的执行流程是这样的:

1、不管前面的函数声明,直到看到eat(),将eat函数添加到调用栈。

2、执行eat函数体内的所有代码,首先会打印‘吃东西啦’,然后看到drink函数,又将其添加到调用栈,然后执行drink,打印‘饮茶啦’,最后打印‘吃饱喝足啦’。

3、eat函数体内的代码执行完毕,将eat移除。

  • 执行栈(execution context stack)

其实就是调用栈。

  • 同步任务与异步任务

同步就是主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

异步任务就是不立即执行,在未来某一刻执行的任务,不进入主线程,而是暂时挂起,等有结果时则会把它对应的回调函数添加到任务队列中。

来看一段代码:

function eat() {

console.log(‘吃东西啦’)

setTimeout(drink, 1000);

console.log(‘吃饱喝足啦’)

}

function drink() {

console.log(‘饮茶啦’)

}

eat()

这里的eat是同步任务,而setTimeout是异步任务,当执行到jsetTimieout时,会将它挂起,直到一秒后将它的回调函数drink添加到任务队列中等待执行。所以,这里的执行结果就会变成这样:

在这里插入图片描述

  • task queue (任务队列)

事件循环有一个或多个任务队列。任务队列是一组任务,而不是一个队列。因为事件循环处理模型的第一步是从任务队列中选择一个可运行的任务,而不是直接拿第一个任务。规范是这么写的:

Task queues are sets, not queues, because step one of the event loop processing model grabs the first runnable task from the chosen queue, instead of dequeuing the first task.

看到这个规范我是有点迷的,以前一直认为任务队列就是一个队列,遵循先进先出的规则的,很多技术文章也是这么写的。这里如果有更好的理解,欢迎评论区留言。

接下来看一张大名鼎鼎的图:

在这里插入图片描述

这是演讲菲利普·罗伯茨:到底什么是Event Loop呢里的一张图。(可以去看下这个演讲,有不忍吐槽的中文字幕,不过要个梯子)遗憾的是,里面并没有讲到宏任务和微任务。

看下代码:

console.log(1)

setTimeout(cb, 2000)

console.log(3)

function cb() {

console.log(2)

}

按照图片的理解,这里就是这么运行的:

1、将console.log(1)压入调用栈中,执行完毕,然后弹出

2、setTimeout压入栈中,发现是异步任务,交给浏览器的其他线程,将他挂起

3、将console.log(3)压入栈中,执行完毕,弹出

4、这时候调用栈是空的,会轮询任务队列里面有没有任务,而如果第二步挂起的setTimeout的两秒还没有过,这时候任务队列就是空的,不执行。只有两秒到了,将cb函数压入任务队列中,事件轮询发现任务队列里面有任务,这时候cb才会被压入栈中执行。

5、以上过程不断重复,也就形成了事件循环。

所以打印的结果就是:

在这里插入图片描述

我们知道,js是单线程的,同一时间能且只能做一件事件,那这些事件的挂起,轮询只能交给浏览器的其他线程去完成了。所以说,是宿主环境给了js异步执行的能力。

  • 宏任务(macrotask)

macroTask,我并没有在规范中找到相关的介绍。不过其他的技术文章有两种说法,一个说法是将任务队列分为宏任务队列和微任务队列,另一个就是说宏任务队列就是任务队列。我个人更偏向于第二种说法,原因稍后说明。

  • 微任务(microtask)

微任务队列不是任务队列,规范是这么说的:

The microtask queue is not a task queue.

这也是上面说的我偏向于第二种说法的原因。每个事件循环都有一个微任务队列,当调用栈为空的时候就会调用微任务队列里面的任务。事件循环一开始微任务队列是空的,过程中会有微任务添加进去,而当主线程为空时,就会执行微任务队列里面的任务。

那哪些宏任务,哪些是微任务呢?没有找到相关的规范文档。不过按照其他的技术文章,一般是这么分类的:

宏任务: script(整体代码),setTimeout, setInterval, setImmediate,I/O, UI rendering

微任务: promise().then, Object.observe, MutationObserver

运行流程

我们来看一下代码:

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值