事件轮询(Event Loop) 宏任务与微任务

事件轮询(Event Loop) 宏任务与微任务

前言

为什么我们要去了解底层事件轮询Event Loop,事件轮询不单单是一道面试题,更多的是因为一个程序员,去了解程序的运行机制这一点是非常重要的,可以让你提升自己的代码能力,前端是一个很宽泛的领域,技术从Javascript问世以后一直都在更新迭代,了解掌握底层的原理可以让你对新的技术相对得心应手。

Javascript诞生

了解事件轮询(Event Loop),就不得不从Javasript诞生的场景说起,Js从出现到到现在都是单线程。我的个人理解是因为需要操作DOM,为了防止DOM冲突,假设多线程的话,有两个任务,同时执行都涉及到DOM,一个修改一个添加DOM,所以需要单线程,并且对于一个脚本语言来说可能就会太复杂,当然(h5中增加了 web work可以用来格外开一个线程,但是同样这个开的线程是受到主线程的控制)本质还是单线程。

Javascript如何执行

1.从前到后,一行一行执行

2.如果某一行执行报错,则停止下面代码的执行

3.先把同步代码执行完,再执行异步

Event Loop是什么?

本质上一种执行的方式,当主线程的任务为空过后,会通过轮询,轮询事件队列(也叫回调队列)当中的任务的方式去执行任务,如果事件队列中的任务为空会一直轮询,通过事件轮询推入主线程。

进程、程序、线程

【1】进程(process):是系统分配的独立资源,是 CPU 资源分配的基本单位,进程是由一个或者多个线程组成的。

【2】程序:是指令和数据的有序集合,进程中的文本区域就是代码区就是程序。

【3】线程(thread):是进程的执行流,是CPU调度和分派的基本单位。

Event Loop的执行过程

Evernt Loop就是异步回调的实现原理,异步要基于回调来实现

在这里插入图片描述

先来看用几行简单的代码来演示一下事件轮询的过程

注:任务队列一开始并不是为空的,因为整体的代码会作为第一个宏任务在任务队列中被循环!

//事件轮询
console.log('Hi');
setTimeout(function cb1(){
    console.log('cb1')
})
console.log('Bye'); 

第一步看代码console.log(‘Hi’)是一个同步代码,将代码推入Call Stack(主线程)(也叫调用栈),进而执行,在控制台中打印Hi
1
在这里插入图片描述
2
在这里插入图片描述

接着往下看代码setTimeout(),我们知道是一个异步的过程,会执行setTimeout这个API,里面有一个回调函数cb1,里面有个定时器不会立马执行,不管有没有带参数ms所以调用setTimeout这个API进入Web APIs(可以理解为web异步调用统☞异步线程)主要是处理web请求。
在这里插入图片描述

在这里插入图片描述

在这个地方放cb1是不会执行的,开启了一个异步线程,主线程就可以不管了,然后把在主线程的任务抽离,接着往下看代码console.log(‘Bye’)同步代码,我们会立即执行在浏览器控制台输出Bye

在这里插入图片描述

这时候Event Loop就会开始工作我们看事件队列(回调队列),异步线程等待定时事件到了过后把任务timer推入事件队列中,然后里面有了cb1这个回调函数, 然后通过事件轮询的方式推入主线程中然后拿到代码,执行
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最后执行完毕然后抽离主线程,到这里应该有一点自己的理解了。

总结Event Loop 过程

1.同步代码,一行一行放在Call Stack执行

2.遇到异步,会先’记录’下,等待(定时、网络请求等等)

3.时机到了,就移动到Callback Queue

4.如果Call Stack为空(同步代码执行完) Event Loop开始工作

5.轮询查找Callback Queue,如有则会移动到Call Stack执行

6.然后继续轮询查找(永动机)

DOM事件与Event Loop

在这里插入图片描述

在第一个圈中的代码中,开启了一个定时器,第二圈中的是使用获取到了一个Btn给绑定了一个点击事件 并且使用了一个回调函数(点击事件,是点击了过后才会被调用的一个回调函数)能否放在Callback Queue中取决于用户是否点击了一个Btn,浏览器会检测,点击了过后才会放入Callback Queue,然后被事件轮询监测到,DOM事件也使用回调是基于Event Loop ,严格意义上DOM事件不是异步。

宏任务 macroTask于微任务microTask

宏任务和微任务,是一个Javascript中的异步进阶,也是进入大厂基本算是必考题,属于异步和单线程的一个知识点。

同样我们先看一段代码

console.log(77)
setTimeout(()=>{
    console.log('H')
})
Promise.resolve().then(()=>{
    console.log('Z')
})
console.log('C')

先思考这里是是promise这个回调先执行,还是setTimeout这个回调先执行?

要想搞懂这个地方所以我们就需要先了解宏任务和微任务。

首先先列举一下常见的

宏任务: setTimeout,setInterval,Ajax,DOM事件 I/O UI交互事件、postMessage、MessageChannel、setImmediate(node.js环境中)

微任务:Promise.then/catch async/await Object.observe MutationObserve precess.nextTick(node.js环境)

看书得知 微任务执行的时机会比宏任务早(这里可以不用强行弄明白,先记住就好)

这里给出一个私人理解:从小到大,微小,先执行微小的然后才是宏观的,像地区试点一样多是从小地方到大地方

所以执行结果自然是
在这里插入图片描述

Event Loop和DOM渲染的关系

Js是单线程的,而且和DOM渲染共用一个线程,JS执行的适合得留一些时机供DOM渲染。

先看一段代码

<body>
 <div class="container"></div>
  <script>
    const container = document.getElementById('container');
    const p1 = '<p>T</p>'
    const p2 = '<p>L</p>'
    const p3 = '<p>Q</p>'
    container.innerHTML = p1;
    container.innerHTML += p2;
    container.innerHTML += p3;
    console.log('length', container.children.length);
	</script>
</body>

执行结果这里就不多赘述,这时候可以回想一下Event Loop的执行过程

在这里插入图片描述

在上面代码中打印hi和bye结束的时候,cb1还在Callback Queue的时候,

我们的Call Stack是空的空闲的,这个时候就会尝试对DOM进行渲染,这个时候就会把DOM渲染,之后再来出发Event Loop简而言之就是同步代码执行完毕过后回去尝试对DOM进行渲染,然后再去触发Event Loop的机制,是一个轮回的过程。

每次Call Stack清空(每次轮询结束),同步任务执行完

都是DOM重新渲染的机会,DOM解构如有改变则重新渲染

然后再去触发一次Event Loop

为什么微任务比宏任务先执行?

同样这里我们先看一段代码,是上面代码的改写

<body>
 <div class="container"></div>
  <script>
    const container = document.getElementById('container');
    const p1 = '<p>T</p>'
    const p2 = '<p>L</p>'
    const p3 = '<p>Q</p>'
    container.innerHTML = p1;
    container.innerHTML += p2;
    container.innerHTML += p3;
    console.log('length', container.children.length);
    alert('本次Call Stack结束,DOM已经更新,但没有触发渲染')
	</script>
</body>

思考一下这个时候会在页面上渲染吗?

答案是不会的

在这里插入图片描述

因为alert函数还在我们的执行栈中,只有点击确定过后,同步代码执行结束过后才会渲染DOM,接着往下面看

宏任务:DOM渲染后触发,如setTimeout

微任务:DOM渲染前触发,如Promise

先进行演示,再考虑原理

<body>
 <div class="container"></div>
  <script>
    const container = document.getElementById('container');
    const p1 = '<p>T</p>'
    const p2 = '<p>L</p>'
    const p3 = '<p>Q</p>'
    container.innerHTML = p1;
    container.innerHTML += p2;
    container.innerHTML += p3;
    console.log('length', container.children.length);
    //alert('本次Call Stack结束,DOM已经更新,但没有触发渲染')
   //DOM渲染之后触发
	setTimeout(()=>{
        const length=container.children.length;
        alert(`macro task 宏 ${length}`)
    })
	//DOM渲染之前触发
    Promise.resolve().then(()=>{
        const length=container.children.length;
         alert(`micro task 微${length}`)
    })
	</script>
</body>

看结果

1.执行微任务的时候其实3个标签已经加载,只是还没有渲染

在这里插入图片描述

2.微任务执行完然后对DOM进行渲染

在这里插入图片描述

这个时候结果就很明显了,我们就刨根问底!

从Event Loop来解释,为什么微任务执行更早

在这里插入图片描述

其实微任务在Call Stack的时候会被推入微任务队列中(micro task queue)而宏任务则会被推向Web APIs,这是为什么呢?

这里可以了解一下Web APIs ,宏任务是由浏览器规定的,而微任务是ES6语法规定的
总结一下:
万物皆从全局上下文准备退出,全局的同步代码执行完毕时,才会执行异步代码(宏任务和微任务)
同一层级下,微任务永远比宏任务先执行,并且会在同层级的宏任务执行前,全部执行结束;
每个宏任务都单独关联了一个任务队列(任务栈),即每次遇到宏任务,都会将其放入一个全新的任务栈中执行;
每层的宏任务都对应了它们各自的微任务队列,微任务永远遵循先进先出原则

最终的版本就是

在这里插入图片描述

所以到这里是不是感觉对Event Loop 微任务宏任务有了新的理解

本文有错的地方还请各位指出,大家一起共同学习。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
浏览器的事件轮询机制是指浏览器在等待事件发生时,采用轮询的方式来检查是否有事件发生。这个机制是浏览器实现异步编程的重要手段之一。在浏览器中,事件可以是用户交互、网络请求、定时器等等。浏览器通过事件队列来管理这些事件,当事件发生时,会将事件加入到事件队列中,然后等待 JavaScript 引擎执行。 事件轮询机制的实现方式是通过一个事件循环来实现的。事件循环会不断地从事件队列中取出事件,然后执行相应的回调函数。当事件队列为空时,事件循环会进入休眠状态,等待新的事件加入到事件队列中。这个过程是不断重复的,直到浏览器关闭。 在事件轮询机制中,有一个重要的概念叫做“任务队列”。任务队列是一个存放任务的队列,每个任务都是一个回调函数。当事件发生时,会将相应的回调函数加入到任务队列中。任务队列分为两种类型:任务任务任务包括用户交互、网络请求、定时器等等,而任务则是指 Promise 的回调函数、MutationObserver 的回调函数等等。 在事件轮询机制中,任务任务的执行顺序是不同的。当一个任务执行完毕后,会立即执行所有的任务,然后再执行下一个任务。这个过程是不断重复的,直到事件队列为空。 总的来说,浏览器的事件轮询机制是一种非常重要的机制,它可以帮助我们实现异步编程,提高程序的性能和用户体验。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值