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

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

文章目录

    • 事件轮询(Event Loop) 宏任务与微任务
        • 前言
        • Javascript诞生
        • Javascript如何执行
        • Event Loop是什么?
        • 进程、程序、线程
        • Event Loop的执行过程
        • 总结Event Loop 过程
        • DOM事件与Event Loop
        • 宏任务 macroTask于微任务microTask
        • Event Loop和DOM渲染的关系
        • 为什么微任务比宏任务先执行?

前言

为什么我们要去了解底层事件轮询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就是异步回调的实现原理,异步要基于回调来实现

事件轮询(Event Loop) 宏任务与微任务_第1张图片

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

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

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

第一步看代码console.log(‘Hi’)是一个同步代码,将代码推入Call Stack(主线程)(也叫调用栈),进而执行,在控制台中打印Hi
1
事件轮询(Event Loop) 宏任务与微任务_第2张图片
2
事件轮询(Event Loop) 宏任务与微任务_第3张图片

接着往下看代码setTimeout(),我们知道是一个异步的过程,会执行setTimeout这个API,里面有一个回调函数cb1,里面有个定时器不会立马执行,不管有没有带参数ms所以调用setTimeout这个API进入Web APIs(可以理解为web异步调用统☞异步线程)主要是处理web请求。
事件轮询(Event Loop) 宏任务与微任务_第4张图片

事件轮询(Event Loop) 宏任务与微任务_第5张图片

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

事件轮询(Event Loop) 宏任务与微任务_第6张图片

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

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

总结Event Loop 过程

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

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

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

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

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

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

DOM事件与Event Loop

事件轮询(Event Loop) 宏任务与微任务_第10张图片

在第一个圈中的代码中,开启了一个定时器,第二圈中的是使用获取到了一个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) 宏任务与微任务_第11张图片

Event Loop和DOM渲染的关系

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

先看一段代码

<body>
 <div class="container"></div>
  <script>
    const container = document.getElementById('container');
    const p1 = '

T

'
const p2 = '

L

'
const p3 = '

Q

'
container.innerHTML = p1; container.innerHTML += p2; container.innerHTML += p3; console.log('length', container.children.length); </script> </body>

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

事件轮询(Event Loop) 宏任务与微任务_第12张图片

在上面代码中打印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 = '

T

'
const p2 = '

L

'
const p3 = '

Q

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

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

答案是不会的

事件轮询(Event Loop) 宏任务与微任务_第13张图片

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

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

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

先进行演示,再考虑原理

<body>
 <div class="container"></div>
  <script>
    const container = document.getElementById('container');
    const p1 = '

T

'
const p2 = '

L

'
const p3 = '

Q

'
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来解释,为什么微任务执行更早

事件轮询(Event Loop) 宏任务与微任务_第14张图片

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

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

最终的版本就是

事件轮询(Event Loop) 宏任务与微任务_第15张图片

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

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

你可能感兴趣的:(前端,javascript,前端,ecmascript)