zoukankan      html  css  js  c++  java
  • javascript事件循环

    前言

    最近看到一道非常经典的面试题,感觉非常有趣:

    setTimeout(() => {
        console.log(1);
    }, 0);
    
    new Promise((resolve) => {
        console.log(2);
        resolve();
    }).then(() => {
        console.log(3);
    });
    
    console.log(4);
    // 输出最后的结果

    正确答案是2 4 3 1,不过这位面试官大佬也不是很专业,只知道答案并不知道原理,决定回家之后研究一番。

    浏览器环境

    单线程与异步

    众所周知,JavaScript是一门单线程的执行语言,处理任务的时候是一件一件的往下处理。

    console.log(1);
    console.log(2);
    console.log(3);
    // 输出结果123

    但大家在往常开发过程中常用的ajax、setTimeout之类的操作并没有阻塞进程:

    console.log(1);
    setTimeout(() => {
        console.log(2);
    }, 0);
    console.log(3);
    // 输出结果1 3 2,setTimeout并没有阻塞后面的流程

    其实JavaScript单线程是指浏览器在解释和执行javascript代码时只有一个线程,即JS引擎线程,浏览器自身还会提供其他线程来支持这些异步方法,浏览器的渲染线程大概有一下几种:

    • JS引擎线程
    • 事件触发线程
    • 定时触发器线程
    • 异步http请求线程
    • GUI渲染线程
    • ...

    浏览器事件机制

    浏览器在执行js代码过程中会维护一个执行栈,每个方法都会进栈执行之后然后出栈(FIFO)。与此同时,浏览器又维护了一个消息队列,所有的异步方法,在执行结束后都会将回调方法塞入消息队列中,当所有执行栈中的任务全部执行完毕后,浏览器开始往消息队列寻找任务,先进入消息队列的任务先执行。

     例如之前的代码的执行步骤:

    console.log(1);
    setTimeout(() => {
        console.log(2);
    }, 0);
    console.log(3);
    • 1.将console.log(1)丢进执行栈,并执行,执行完毕出栈。
    • 2.将setTimeout丢给浏览器异步进程执行。
    • 3.console.log(3)丢到执行栈,执行,执行完毕出栈。
    • 4.setTimeout时间到,把回调丢给消息队列。
    • 5.此时执行栈为空,从消息队列取任务执行,console.log(2)执行。

    宏任务和微任务

    那么如果两个不同种类的异步任务执行后,哪个会先执行?就像开头提到的面试题,setTimeout和promise哪个会先执行?这时候要提到概念:宏任务微任务。 概念如下:

    • 宏任务:js同步执行的代码块,setTimeout、setInterval、XMLHttprequest等。
    • 微任务:promise、process.nextTick(node环境)等。

    执行栈中执行的任务都是宏任务,当宏任务遇到Promise的时候会创建微任务,当Promise状态fullfill的时候塞入微任务队列。在一次宏任务完成后,会检查微任务队列有没有需要执行的任务,有的话按顺序执行微任务队列中所有的任务。之后再开始执行下一次宏任务。具体步骤:

    1. 执行主代码块
    2. 若遇到Promise,把then之后的内容放进微任务队列
    3. 一次宏任务执行完成,检查微任务队列有无任务
    4. 有的话执行所有微任务
    5. 执行完毕后,开始下一次宏任务。
    setTimeout(() => {
        console.log(1);
    }, 0);
    
    new Promise((resolve) => {
        console.log(2);
        resolve();
    }).then(() => {
        console.log(3);
    });
    
    console.log(4);

    这道面试题的步骤:

    1. setTimeout丢给浏览器的异步线程处理,因为时间是0,马上放入消息队列
    2. new Promise里面的console.log(2)加入执行栈,并执行,然后退出
    3. 直接resolve,then后面的内容加入微任务队列
    4. console.log(4)加入执行栈,执行完成后退出
    5. 检查微任务队列,发现有任务,执行console.log(3)
    6. 发现消息队列有任务,执行下一次宏任务console.log(1)

    node环境

    node环境中的事件机制要比浏览器复杂很多,node的事件轮询有阶段的概念。每个阶段切换的时候执行,process.nextTick之类的所有微任务。

    timer阶段

    执行所有的时间已经到达的计时事件

    peding callbacks阶段

    这个阶段将执行所有上一次poll阶段没有执行的I/O操作callback,一般是报错。

    idle.prepare

    可以忽略

    poll阶段

    这个阶段特别复杂

    1. 阻塞等到所有I/O操作,执行所有的callback.
    2. 所有I/O回调执行完,检查是否有到时的timer,有的话回到timer阶段
    3. 没有timer的话,进入check阶段.

    check阶段

    执行setImmediate

    close callbacks阶段

    执行所有close回调事件,例如socket断开。

  • 相关阅读:
    spring-boot快速搭建解析
    springmvc处理ajax跨域
    Spring-AOP解析
    springmvc注解
    springmvc源码分析
    Spring源码分析
    JAVA性能优化
    数据取舍
    命令行控制
    Junit常用操作
  • 原文地址:https://www.cnblogs.com/magicg/p/13630157.html
Copyright © 2011-2022 走看看