JavaScript(五): 事件循环


我们面试的时候经常会问到事件循环,也就是event loop。很多时候我们都是一脸懵,我们通常会背关于事件循环的面试题,讲给面试官的时候自己都不知道自己在讲什么,可能面试官也不太了解事件循环,只是看别人都这么问。那么,仔细了解一下事件循环吧,对以后的编程真的会有帮助的。

1.为什么js是单线程?

js作为主要运行在浏览器的脚本语言,js主要用途之一是操作DOM。

举一个例子,如果js同时有两个线程,同时对同一个dom进行操作,这时浏览器应该听哪个线程的,如何判断优先级?

为了避免这种问题,js必须是一门单线程语言,并且在未来这个特点也不会改变。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

var i, t = Date.now()
for (i = 0; i < 100000000; i++) {
    
    }
console.log(Date.now() - t) // 238

像上面这样,如果排队是因为计算量大,CPU忙不过来,但是,如果是网络请求就不合适。因为一个网络请求的资源什么时候返回是不可预知的,这种情况再排队等待就不明智了。

所以出现了同步与异步。

2.同步和异步

同步

同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。

// 同步代码
function fun1() {
    
    
  console.log(1);
}
function fun2() {
    
    
  console.log(2);
}
fun1();
fun2();

输出会依次输入1,2,因为代码是从上到下依次执行,执行完fun1(),才继续执行fun2()。

扫描二维码关注公众号,回复: 11949031 查看本文章

异步

异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。

function fun1() {
    
    
  console.log(1);
}
function fun2() {
    
    
  console.log(2);
}
function fun3() {
    
    
  console.log(3);
}
fun1();
setTimeout(function(){
    
    
  fun2();
},0);
fun3();

依次输出1,3,2,因为我们会优先执行同步函数,然后在执行异步函数。

正是由于JavaScript是单线程的,而异步容易实现非阻塞,所以在JavaScript中对于耗时的操作或者时间不确定的操作,使用异步就成了必然的选择。
  
JavaScript的执行顺序:(重点)

1.先同步后异步。
2.异步中任务队列的执行顺序: 先微任务microtask队列,再宏任务macrotask队列。
3.调用Promise 中的resolve,reject属于微任务队列,setTimeout属于宏任务队列。

那什么是微任务和宏任务?

3.宏任务与微任务

异步任务分为 宏任务(macrotask) 与 微任务 (microtask),不同的API注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。

宏任务

macrotask,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。

浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个macrotask执行结束后,在下一个macrotask 执行开始前,对页面进行重新渲染,流程如下:

macrotask->渲染->macrotask->...

宏任务包含:

script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)

微任务

microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。

所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。

微任务包含:

Promise.then
Object.observe
MutaionObserver
process.nextTick(Node.js 环境)

4.Event Loop(事件循环)

Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:

1.执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行。
2.检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列。
3.更新render(每一次事件循环,浏览器都可能会去更新渲染)。
4.重复以上步骤。

宏任务 > 所有微任务 > 宏任务,如下图所示:

在这里插入图片描述

从代码执行顺序的角度来看,程序最开始是按代码顺序执行代码的,遇到同步任务,立刻执行;遇到异步任务,则只是调用异步函数发起异步请求。此时,异步任务开始执行异步操作,执行完成后到消息队列中排队。程序按照代码顺序执行完毕后,查询消息队列中是否有等待的消息。如果有,则按照次序从消息队列中把消息放到执行栈中执行。执行完毕后,再从消息队列中获取消息,再执行,不断重复。

由于主线程不断的重复获得消息、执行消息、再取消息、再执行。所以,这种机制被称为事件循环。
  
  用代码表示:

while (queue.waitForMessage()) {
    
    
  queue.processNextMessage();
}

如果当前没有任何消息queue.waitForMessage 会等待同步消息到达。

5.实例

下面以一个实例来解释事件循环机制:

console.log(1)
div.onclick = () => {
    
    console.log('click')}
console.log(2)
setTimeout(() => {
    
    console.log('timeout')},1000)

1、执行第一行代码,第一行是一个同步任务,控制台显示1

2、执行第二行代码,第二行是一个异步任务,发起异步请求,可以在任意时刻执行鼠标点击的异步操作

3、执行第三行代码,第三行是一个同步任务,控制台显示2

4、执行第四行代码,第四行是一个异步任务,发起异步请求,1s后执行定时器任务

5、假设从执行第四行代码的1s内,执行了鼠标点击,则鼠标任务在消息队列中排到首位

6、从执行第四行代码1s后,定时器任务到消息队列中排到第二位

7、现在同步任务已经执行完毕,则从消息队列中按照次序把异步任务放到执行栈中执行

8、则控制台依次显示’click‘、‘timeout’

9、过了一段时间后,又执行了一次鼠标点击,由于消息队列中已经空了,则鼠标任务在消息队列中排到首位

10、同步任务执行完毕后,再从消息队列中按照次序把异步任务放到执行栈中执行

11、 则控制台显示’click’

异步过程

下面以一个实例来解释一次完整的异步过程:

div.onclick = function fn(){
    
    console.log('click')}

1、主线程通过调用异步函数div.onclick发起异步请求

2、在某一时刻,执行异步操作,即鼠标点击

3、接着,回调函数fn到消息队列中排队

4、主线程从消息队列中读取fn到执行栈中

5、然后在执行栈中执行fn里面的代码console.log(‘click’)

6、于是,控制台显示’click’

例题: 依次输出什么?

    function fun1() {
    
    
        console.log("1")
    }

    fun1()

    setTimeout(function () {
    
    
        console.log('2')
    });

    function fun3() {
    
    
        console.log("3")
    }
    fun3()
    var l4 = new Promise(function (resolve) {
    
    
        for (var i = 0; i < 10000; i++) {
    
    
            i == 99 && resolve();
        }
    }).then(function () {
    
    
        console.log('4')
    });

    console.log('5');

猜你喜欢

转载自blog.csdn.net/liuyifeng0000/article/details/105210146