JS事件循环机制

更新日期: 2019-07-29阅读: 1.7k标签: 机制

js是一门单线程的编程语言,也就是说js在处理任务的时候,所有任务只能在一个线程上排队被执行,那如果某一个任务耗时比较长呢?总不能等到它执行结束再去执行下一个。
所以在线程之内,又被分为了两个队列:

  • 同步任务队列
  • 异步任务队列

举个例子来说:比如你去银行办理业务,都需要领号排队。银行柜员一个个办理业务,这时这个柜员就相当于一个js线程,客户排的队就相当于同步任务队列,每个人对于柜员相当于一个个的任务。
但这个时候,你的电话突然响了,你去接电话接了半小时。这时候人家柜员一看你这情况,直接叫了下一个,而你领的号就作废了,只能重新零号排队。这时候你就是被分发到了异步任务队列。
等你前边的人都完事了,柜员把你叫过去办了你的业务,这时候就是同步队列中的任务执行完了,主线程会处理异步队列中的任务。


同步任务和异步任务

这里说的异步任务,它的意思是包含了独立于主执行栈之外的宏任务和微任务。

先看一个简单的例子,对这样的执行机制有个简单的认识:

console.log('start')

console.log('end')

上边的执行结果大家肯定都明白,先输出start,再输出end,这一段代码会进入同步队列,顺序执行。

那么我们加点料:

console.log('start')

setTimeout(function() {
    console.log('setTimeout')
}, 0)

console.log('end')

这样的情况,函数调用栈执行到setTimeout时,setTimeout会在规定的时间点将回调函数放入异步队列,等待同步队列的任务被执行完,立即执行,所以结果是:start、end、setTimeout。

但需要注意的一点是,普遍认为setTimeout定时执行的认知是片面的,因为假设setTimeout规定2秒后执行,但同步队列中有一个函数,执行花了很长时间,甚至花了1秒。那么这时setTimeout中的回调也会等上至少1秒之后,同步任务都执行完了,再去执行。这时候的setTimeout回调执行的时机就会超过2秒,也就是至少3秒。


宏任务与微任务

宏任务与微任务都是独立与主执行栈之外的另外两个队列,可以在概念上划分在异步任务队列里。而这些队列由js的事件循环(EventLoop)来搞定

macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

由于写文章时没有注意到,实际上宏任务与微任务的概念是不准确的,但由于文章中涉及多处宏任务、微任务的解读,所以本文暂时还是用宏任务、微任务来分别代指task、jobs。但读者要明白规范中没有宏任务的概念,只有task与jobs

其中宏任务(task)包括:

  • script(整体代码)
  • setTimeout, setInterval, setImmediate,
  • I/O
  • UI rendering

ajax请求不属于宏任务,js线程遇到ajax请求,会将请求交给对应的http线程处理,一旦请求返回结果,就会将对应的回调放入宏任务队列,等请求完成执行。

微任务(jobs)包括:

  • process.nextTick
  • Promise
  • Object.observe(已废弃)
  • MutationObserver(html5新特性)

这些我们可以理解为它们在执行上下文中都是可执行代码,会立即执行,只不过会将各自的回调函数放入对应的任务队列中(宏任务微任务),也就相当于一个调度者。

我们梳理一下事件循环的执行机制:
循环首先从宏任务开始,遇到script,生成执行上下文,开始进入执行栈,可执行代码入栈,依次执行代码,调用完成出栈。
执行过程中遇到上边提到的调度者,会同步执行调度者,由调度者将其负责的任务(回调函数)放到对应的任务队列中,直到主执行栈清空,然后开始执行微任务的任务队列。微任务也清空后,再次从宏任务开始,一直循环这一过程。


示例

上边说了那么多,还是用一些代码来验证一下是否是这样的,先来一个简单一点的。

console.log('start')

setTimeout(function() {
    console.log('timeout')
}, 0)

new Promise(function(resolve) {
    console.log('promise')
    resolve()
}).then(function() {
    console.log('promise resolved')
})

console.log('end')

根据上边的结论,分析一下执行过程:

  • 建立执行上下文,进入执行栈开始执行代码,打印start
  • 往下执行,遇到setTimeout,将回调函数放入宏任务队列,等待执行
  • 继续往下,有个new Promise,其回调函数并不会被放入其他任务队列,因此会同步地执行,打印promise,但是当resolve后,.then会把其内部的回调函数放入微任务队列
  • 执行到了最底部的代码,打印出end。这时,主执行栈清空了,开始寻找微任务队列里有没有可执行代码
  • 发现了微任务队列中有之前放进去的代码,执行打印出promise resolved,第一次循环结束
  • 再开始第二次循环,从宏任务开始,检查宏任务队列是否有可执行代码,发现有一个,打印timeout

所以,打印顺序是:start-->promise-->end-->promise resolved-->timeout

上边是一个简单示例,比较好理解。那么接下来看一个稍微复杂一点的(这里直接用汉字直观地表明了打印的时机,避免看起来费劲):

console.log('第一次循环主执行栈开始')

setTimeout(function() {
    console.log('第二次循环开始,宏任务队列的第一个宏任务执行中')
    new Promise(function(resolve) {
        console.log('宏任务队列的第一个宏任务的微任务继续执行')
        resolve()
    }).then(function() {
        console.log('第二次循环的微任务队列的微任务执行')
    })
}, 0)

new Promise(function(resolve) {
    console.log('第一次循环主执行栈进行中...')
    resolve()
}).then(function() {
    console.log('第一次循环微任务,第一次循环结束')
    setTimeout(function() {
        console.log('第二次循环的宏任务队列的第二个宏任务执行')
    })
})

console.log('第一次循环主执行栈完成')

同样我们分析一下执行过程:

  • 第一次循环

    1. 进入执行栈执行代码,打印第一次循环主执行栈开始
    2. 遇到setTimeout,将回调放入宏任务队列等待执行
    3. promise声明过程是同步的,打印第一次循环主执行栈进行中...,resolve后遇到.then,将回调放入微任务队列
    4. 打印第一次循环主执行栈完成
    5. 检查微任务队列是否有可执行代码,有一个第三步放入的任务,打印第一次循环微任务,第一次循环结束,第一次循环结束,同时遇到setTimeout,将回调放入宏任务队列
  • 第二次循环

    1. 从宏任务入手,检查宏任务队列,发现有两个宏任务,分别是第一次循环第二步和第一次循环第五步被放入的任务,先执行第一个宏任务,打印第二次循环开始,宏任务队列的第一个宏任务执行中
    2. 遇到promise声明语句,打印宏任务队列的第一个宏任务继续执行,这时候又被resolve了,又会将.then中的回调放入微任务队列,这是这个宏任务队列中的第一个任务还没执行完
    3. 第一个宏任务中的同步代码执行完毕,检查微任务队列,发现有一段第二步放进去的代码,执行打印第二次循环的微任务队列的微任务执行,此时第一个宏任务执行完毕
    4. 开始执行第二个宏任务,打印第二次循环的宏任务队列的第二个宏任务执行,所有任务队列全部清空,执行完毕

所以打印顺序为:

  • 第一次循环主执行栈开始
  • 第一次循环主执行栈进行中...
  • 第一次循环主执行栈完成
  • 第一次循环微任务,第一次循环结束
  • 第二次循环开始,宏任务队列的第一个宏任务执行中
  • 第二次循环的宏任务队列的第一个宏任务的微任务继续执行
  • 第二次循环的微任务队列的微任务执行
  • 第二次循环的宏任务队列的第二个宏任务执行



链接: https://www.fly63.com/article/detial/4395

浅析前端页面渲染机制

作为一个前端开发,最常见的运行环境应该是浏览器吧,为了更好的通过浏览器把优秀的产品带给用户,也为了更好的发展自己的前端职业之路,有必要了解从我们在浏览器地址栏输入网址到看到页面这期间浏览器是如何进行工作的

这一次,彻底弄懂 JavaScript 执行机制

javascript是一门单线程语言,Event Loop是javascript的执行机制.牢牢把握两个基本点,以认真学习javascript为中心,早日实现成为前端高手的伟大梦想!

创建js hook钩子_js中的钩子机制与实现

钩子机制也叫hook机制,或者你可以把它理解成一种匹配机制,就是我们在代码中设置一些钩子,然后程序执行时自动去匹配这些钩子;这样做的好处就是提高了程序的执行效率,减少了if else 的使用同事优化代码结构

小程序的更新机制_如何实现强制更新?

在讲小程序的更新机制之前,我们需要先了解小程序的2种启动模式,分别为:冷启动和热启动。小程序不同的启动方式,对应的更新情况不不一样的。无论冷启动,还是热启动。小程序都不会马上更新的,如果我们需要强制更新,需要如何实现呢?

基于JWT的Token认证机制实现及安全问题

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其JWT的组成:一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

web前端-JavaScript的运行机制

本文介绍JavaScript运行机制,JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。

轮询机制解决后端任务回调问题

现在有一个需求,前端有一个按钮,点击以后会调用后端一个接口,这个接口会根据用户的筛选条件去hadoop上跑任务,将图片的base64转为img然后打包成zip,生成一个下载连接返回给前端,弹出下载框。hadoop上的这个任务耗时比较久

JavaScript预解释是一种毫无节操的机制

js代码执行之前,浏览器首先会默认的把所有带var和function的进行提前的声明或者定义:1.理解声明和定义、2.对于带var和function关键字的在预解释的时候操作不一样的、3.预解释只发生在当前的作用域下

js对代码解析机制

脚本执行js引擎都做了什么呢?1.语法分析 2.预编译 3.解释执行。在执行代码前,还有两个步骤;语法分析很简单,就是引擎检查你的代码有没有什么低级的语法错误 ,查找全局变量声明(包括隐式全局变量声明,省略var声明),变量名作全局对象的属性,值为undefined

web认证机制

以前对认证这方面的认识一直不太深刻,不清楚为什么需要token这种认证,为什么不简单使用session存储用户登录信息等。最近读了几篇大牛的博客才对认证机制方面有了进一步了解。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!