为什么我们要去了解底层事件轮询Event Loop,事件轮询不单单是一道面试题,更多的是因为一个程序员,去了解程序的运行机制这一点是非常重要的,可以让你提升自己的代码能力,前端是一个很宽泛的领域,技术从Javascript问世以后一直都在更新迭代,了解掌握底层的原理可以让你对新的技术相对得心应手。
了解事件轮询(Event Loop),就不得不从Javasript诞生的场景说起,Js从出现到到现在都是单线程。我的个人理解是因为需要操作DOM,为了防止DOM冲突,假设多线程的话,有两个任务,同时执行都涉及到DOM,一个修改一个添加DOM,所以需要单线程,并且对于一个脚本语言来说可能就会太复杂,当然(h5中增加了 web work可以用来格外开一个线程,但是同样这个开的线程是受到主线程的控制)本质还是单线程。
1.从前到后,一行一行执行
2.如果某一行执行报错,则停止下面代码的执行
3.先把同步代码执行完,再执行异步
本质上一种执行的方式,当主线程的任务为空过后,会通过轮询,轮询事件队列(也叫回调队列)当中的任务的方式去执行任务,如果事件队列中的任务为空会一直轮询,通过事件轮询推入主线程。
【1】进程(process):是系统分配的独立资源,是 CPU 资源分配的基本单位,进程是由一个或者多个线程组成的。
【2】程序:是指令和数据的有序集合,进程中的文本区域就是代码区就是程序。
【3】线程(thread):是进程的执行流,是CPU调度和分派的基本单位。
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这个回调函数, 然后通过事件轮询的方式推入主线程中然后拿到代码,执行
最后执行完毕然后抽离主线程,到这里应该有一点自己的理解了。
1.同步代码,一行一行放在Call Stack执行
2.遇到异步,会先’记录’下,等待(定时、网络请求等等)
3.时机到了,就移动到Callback Queue
4.如果Call Stack为空(同步代码执行完) Event Loop开始工作
5.轮询查找Callback Queue,如有则会移动到Call Stack执行
6.然后继续轮询查找(永动机)
在第一个圈中的代码中,开启了一个定时器,第二圈中的是使用获取到了一个Btn给绑定了一个点击事件 并且使用了一个回调函数(点击事件,是点击了过后才会被调用的一个回调函数)能否放在Callback Queue中取决于用户是否点击了一个Btn,浏览器会检测,点击了过后才会放入Callback Queue,然后被事件轮询监测到,DOM事件也使用回调是基于Event Loop ,严格意义上DOM事件不是异步。
宏任务和微任务,是一个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环境)
看书得知 微任务执行的时机会比宏任务早(这里可以不用强行弄明白,先记住就好)
这里给出一个私人理解:从小到大,微小,先执行微小的然后才是宏观的,像地区试点一样多是从小地方到大地方
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的执行过程
在上面代码中打印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>
思考一下这个时候会在页面上渲染吗?
答案是不会的
因为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来解释,为什么微任务执行更早
其实微任务在Call Stack的时候会被推入微任务队列中(micro task queue)而宏任务则会被推向Web APIs,这是为什么呢?
这里可以了解一下Web APIs ,宏任务是由浏览器规定的,而微任务是ES6语法规定的
总结一下:
万物皆从全局上下文准备退出,全局的同步代码执行完毕时,才会执行异步代码(宏任务和微任务)
同一层级下,微任务永远比宏任务先执行,并且会在同层级的宏任务执行前,全部执行结束;
每个宏任务都单独关联了一个任务队列(任务栈),即每次遇到宏任务,都会将其放入一个全新的任务栈中执行;
每层的宏任务都对应了它们各自的微任务队列,微任务永远遵循先进先出原则
最终的版本就是
所以到这里是不是感觉对Event Loop 微任务宏任务有了新的理解
本文有错的地方还请各位指出,大家一起共同学习。