# 浏览器中的 JavaScript 线程

  • 我们经常会说 JavaScript 是单线程的,但是 JavaScript 的线程应该有自己的容器进程:浏览器或者 Node。
  • 浏览器是一个进程吗,它里面只有一个线程吗?
    • 目前多数的浏览器其实都是多进程的,当我们打开一个 tab 页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出;
    • 每个进程中又有很多的线程,其中包括执行 JavaScript 代码的线程;
  • JavaScript 的代码执行是在一个单独的线程中执行的:
    • 这就意味着 JavaScript 的代码,在同一个时刻只能做一件事;
    • 如果这件事是非常耗时的,就意味着当前的线程就会被阻塞;
  • 所以真正耗时的操作,实际上并不是由 JavaScript 线程在执行的:
    • 浏览器的每个进程是多线程的,那么其他线程可以来完成这个耗时的操作;
    • 比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可;

# 宏任务和微任务

  • 但是事件循环中并非只维护着一个队列,事实上是有两个队列:
    • 宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM 监听、UI Rendering 等
    • 微任务队列(microtask queue):Promise 的 then 回调、 Mutation Observer API、queueMicrotask () 等
  • 那么事件循环对于两个队列的优先级是怎么样的呢?
    • 1.main script 中的代码优先执行(编写的顶层 script 代码);
    • 2. 在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
      • 也就是宏任务执行之前,必须保证微任务队列是空的;
      • 如果不为空,那么就优先执行微任务队列中的任务(回调);

# 事件循环面试题一

  1. 事件循环面试题

    面试题
    setTimeout(function () {
      console.log("setTimeout1");
      new Promise(function (resolve) {
        resolve();
      }).then(function () {
        new Promise(function (resolve) {
          resolve();
        }).then(function () {
          console.log("then4");
        });
        console.log("then2");
      });
    });
    new Promise(function (resolve) {
      console.log("promise1");
      resolve();
    }).then(function () {
      console.log("then1");
    });
    setTimeout(function () {
      console.log("setTimeout2");
    });
    console.log(2);
    queueMicrotask(() => {
      console.log("queueMicrotask1")
    });
    new Promise(function (resolve) {
      resolve();
    }).then(function () {
      console.log("then3");
    });

    下面正确的的输出结果是:A

    • promise1 2 then1 queueMicrotask1 then3 setTimeout1 then2 then4 setTimtout2
    • 2 queueMicrotask1 setTimeout1 then2 then4 promise1 then1 setTimeout2 then3
    • 2 queueMicrotask1 promise1 then1 then3 setTimeout1 then2 then4 setTimeout2

    初始任务队列:
    main script:promise1 回调函数 2
    microtask: Promise1 queueMicrotask Promise2
    macrotask:setTimeout1 setTimeout2

# 事件循环面试题二

  1. 事件循环面试题

    面试题
    async function bar() {
      console.log("樱岛麻衣")
      return new Promise(resolve => resolve())
    }
    async function foo() {
      console.log("入间同学")
      await bar()
      console.log("稚名真白")
    }
    foo()
    console.log("萌王")

    下面正确的的输出结果是:C

    • 萌王 入间同学 稚名真白 樱岛麻衣
    • 萌王 入间同学 樱岛麻衣 稚名真白
    • 入间同学 樱岛麻衣 萌王 稚名真白

# 事件循环面试题三

  1. 事件循环面试题

    面试题
    async function async1 () {
      console.log('async1Start')
      await async2();
      console.log('async1End')
    }
    async function async2 () {
      console.log('async2')
    }
    console.log('scriptStart')
    setTimeout(function () {
      console.log('setTimeout')
    }, 0)
     
    async1()
     
    new Promise (function (resolve) {
      console.log('promise1')
      resolve();
    }).then (function () {
      console.log('promise2')
    })
    console.log('scriptEnd')

    下面正确的的输出结果是:B

    • scriptStart async1Start async2 async1End promise1 promise2 scriptEnd setTimeout
    • scriptStart async1Start async2 promise1 scriptEnd async1End promise2 setTimeout
    • scriptstart promise1 scriptEnd async1Start async2 async1End setTimeout promise2

# 事件循环面试题四

  1. 事件循环面试题

    面试题
    Promise.resolve().then(() => {
      console.log('樱岛麻衣')
      return '稚名真白'
    }).then((res) => {
      console.log(res)
    })
    Promise.resolve().then(() => {
      console.log('和泉纱雾')
    }).then(() => {
      console.log('四系乃')
    }).then(() => {
      console.log('鸢一折纸')
    }).then(() => {
      console.log('牧濑红莉栖')
    }).then(() =>{
      console.log('小鸟游六花')
    })

    下面正确的的输出结果是:A

    • 樱岛麻衣 和泉纱雾 稚名真白 四系乃 鸢一折纸 牧濑红莉栖 小鸟游六花
    • 樱岛麻衣 稚名真白 和泉纱雾 四系乃 鸢一折纸 牧濑红莉栖 小鸟游六花
    • 樱岛麻衣 和泉纱雾 四系乃 稚名真白 鸢一折纸 牧濑红莉栖 小鸟游六花

# 事件循环面试题五

  1. 事件循环面试题

    面试题
    Promise.resolve().then(() => {
      console.log('樱岛麻衣')
      return {
        then: (resolve) => {
          resolve('稚名真白')
        }
      }
    }).then((res) => {
      console.log(res)
    })
    Promise.resolve().then(() => {
      console.log('和泉纱雾')
    }).then(() => {
      console.log('四系乃')
    }).then(() => {
      console.log('鸢一折纸')
    }).then(() => {
      console.log('牧濑红莉栖')
    }).then(() =>{
      console.log('小鸟游六花')
    })

    下面正确的的输出结果是:C

    • 樱岛麻衣 和泉纱雾 稚名真白 四系乃 鸢一折纸 牧濑红莉栖 小鸟游六花
    • 樱岛麻衣 和泉纱雾 稚名真白 四系乃 鸢一折纸 牧濑红莉栖 小鸟游六花
    • 樱岛麻衣 和泉纱雾 四系乃 稚名真白 鸢一折纸 牧濑红莉栖 小鸟游六花

    thenable 会推迟一步微任务

# 事件循环面试题六

  1. 事件循环面试题

    面试题
    Promise.resolve().then(() => {
      console.log('樱岛麻衣')
      return Promise.resolve('稚名真白')
    }).then(res => console.log(res))
    Promise.resolve().then(() => {
      console.log('和泉纱雾')
    }).then(() => {
      console.log('四系乃')
    }).then(() => {
      console.log('鸢一折纸')
    }).then(() => {
      console.log('牧濑红莉栖')
    }).then(() =>{
      console.log('小鸟游六花')
    })

    下面正确的的输出结果是:C

    • 樱岛麻衣 和泉纱雾 稚名真白 四系乃 鸢一折纸 牧濑红莉栖 小鸟游六花
    • 樱岛麻衣 和泉纱雾 四系乃 稚名真白 鸢一折纸 牧濑红莉栖 小鸟游六花
    • 樱岛麻衣 和泉纱雾 四系乃 鸢一折纸 稚名真白 牧濑红莉栖 小鸟游六花

    Promise 会推迟两步微任务

# Node 事件循环的阶段

  • 我们最前面就强调过,事件循环像是一个桥梁,是连接着应用程序的 JavaScript 和系统调用之间的通道:
    • 无论是我们的文件 IO、数据库、网络 IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中;
    • 事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行;
  • 但是一次完整的事件循环 Tick 分成很多个阶段:
    • 定时器(Timers):本阶段执行已经被 setTimeout () 和 setInterval () 的调度回调函数。
    • 待定回调(Pending Callback):对某些系统操作(如 TCP 错误类型)执行回调,比如 TCP 连接时接收到 ECONNREFUSED。
    • idle, prepare:仅系统内部使用。
    • 轮询(Poll):检索新的 I/O 事件;执行与 I/O 相关的回调;
    • 检测(check):setImmediate () 回调函数在这里执行。
    • 关闭的回调函数:一些关闭的回调函数,如:socket.on ('close', ...)

# Node 的宏任务和微任务

  • 我们会发现从一次事件循环的 Tick 来说,Node 的事件循环更复杂,它也分为微任务和宏任务:
    • 宏任务(macrotask):setTimeout、setInterval、IO 事件、setImmediate、close 事件;
    • 微任务(microtask):Promise 的 then 回调、process.nextTick、queueMicrotask;
  • 但是,Node 中的事件循环不只是 微任务队列和 宏任务队列:
    • 微任务队列:
      • next tick queue:process.nextTick;
      • other queue:Promise 的 then 回调、queueMicrotask;
    • 宏任务队列:
      • timer queue:setTimeout、setInterval;
      • poll queue:IO 事件;
      • check queue:setImmediate;
      • close queue:close 事件;
  • Node 事件循环的顺序
    • next tick microtask queue
    • pother microtask queue
    • ptimer queue
    • ppoll queue
    • pcheck queue
    • pclose queue

# 事件循环面试题七

  1. Node 事件循环面试题

    面试题
    async function async1() {
      console.log('async1Start')
      await async2()
      console.log('async1End')
    }
    async function async2() {/
      console.log('async2')
    }
    console.log('scriptStart')
    setTimeout(function () {
      console.log('setTimeout1')
    }, 0)
    setTimeout(function () {
      console.log('setTimeout2')
    }, 300)
    setImmediate(() => console.log('setImmediate'))
    process.nextTick(() => console.log('nextTick1'))
    async1();
    process.nextTick(() => console.log('nextTick2'))
    new Promise(function (resolve) {
      console.log('promise1')
      resolve();
      console.log('promise2')
    }).then(function () {
      console.log('promise3')
    })
    console.log('scriptEnd')
    setTimeout(() => {
      console.log('setTimeout3')
    }, 1000)

    下面正确的的输出结果是:C

    • scriptStart async1Start async2 promise1 promise2 scriptEnd nextTick1 async1End nextTick2 promise3 setImmediate setTimeout1 setTimeout2 setTimeout3
    • scriptStart async1Start async2 promise1 promise2 scriptEnd nextTick1 async1End nextTick2 promise3 setTimeout1 setImmediate setTimeout2 setTimeout3
    • scriptStart async1Start async2 promise1 promise2 scriptEnd nextTick1 nextTick2 async1End promise3 setTimeout1 setImmediate setTimeout2 setTimeout3

    初始队列

    mainScript:scriptStart async1Start async2 promise1 promise2 scriptEnd

    nextTick:nextTick1 nextTick2

    otherMicro:async1End promise3

    timers:setTimeout1 300ms -> setTimeout2 1000ms -> setTimeout3

    check:setImmediate

# 事件循环面试题八

  1. Node 事件循环面试题
    面试题
    setTimeout(function () {
      console.log('setTimeout1')
    }, 1)
    setTimeout(() => {
      console.log('setTimeout2')
    }, 2)
    setTimeout(function () {
      console.log('setTimeout3')
    }, 3)
    setTimeout(function () {
      console.log('setTimeout4')
    }, 1)
    setImmediate(() => console.log('setImmediate'))
    下面正确的的输出结果是:D
    • setImmediate setTimeout1 setTimeout4 setTimeout2 setTimeout3
    • setTimeout1 setImmediate setTimeout4 setTimeout2 setTimeout3
    • setTimeout1 setTimeout4 setImmediate setTimeout2 setTimeout3
    • 以上都不对

经过多次测试,发现出现了多种答案,这里面涉及的比较复杂,目前无法解答,建议问度娘