浏览器 事件循环

177 阅读1分钟

浏览器事件循环

js在浏览器有事件循环机制的原因

  • js是单线程

  • 通过 event loop 实现异步、非阻塞事件

事件循环的任务

  • 宏任务

    • 将任务排到下一个事件循环

    • 代码块、setTimeout、setInterval、setImmediate(Node)、requestAnimationFrame(浏览器)、IO操作、网络请求

    image.png

  • 微任务

    • 将任务排到当前事件循环的队尾

    • new Promise().then、Object.observe、MutationObserver(mutation 突变,前端的回溯)、process.nextTick(Node)

      • MutaionObserver

        let observer = new MutationObserver(()=>{
          console.log(1);
        })
        let node = document.createElement('div')
        observer.observe(node, { // 监听节点
          childList: true // 一旦改变则触发回调函数 nextTickHandler
        })
        node.innerHTML = 1
        
      • MutaionObserver封装一个微任务执行函数

        let nextTick = (function () {
          let callbacks = []
          function nextTickHandler() {
            let copies = callbacks.slice(0)
            callbacks = []
            for (let i = 0; i < copies.length; i++) {
              copies[i]()
            }
          }
        
          let counter = 1
          let observer = new MutationObserver(nextTickHandler) // 声明 MO 和回调函数
          let node = document.createElement('div')
          observer.observe(node, { // 监听节点
            childList: true // 一旦改变则触发回调函数 nextTickHandler
          })
          return function (cb) {
            callbacks.push(cb)
            counter = (counter + 1) % 2 //让节点内容文本在 1 和 0 间切换
            node.innerHTML = counter
          }
        })()
        
  • 完成宏任务后去清空微任务队列

  • 微任务存在的原因

    • 宏任务:先进先出

    • 执行宏任务(页面变化)时,用来检测判断对象发生变化的

宏任务&微任务-事件循环

  • Event Loop(事件循环)

    • 同步的进入主线程,异步的进入 Event Table 并注册函数

    • 完成指定的事情后,Event Table 会将这个函数移入 Event Queue

    • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行

    • 上述过程会不断重复

  • 执行

    • 先放入:宏任务一个eventqueue,再微任务另一个eventqueue

    • 外拿时:先从微任务里拿回掉函数,再从宏任务的queue上拿宏任务的回掉函数

Node中的事件循环和浏览器中事件循环的区别

  • 宏任务执行顺序

    • timers 定时器

      • 执行已经安排的setTimeout和setInterval的回调函数
    • pending callback 待定回调

      • 执行延迟到下一个循环迭代的I/O回调
    • idle,prepare

      • 仅系统内部使用
    • poll 轮询

      • 检索新的I/O事件,执行相关的回调
    • check

      • 执行 setImmediate() 回调函数
    • close callback

      • socket.on('close',() => {})
  • 微任务和宏任务在node的执行顺序

    • Node v10 之前

      • 执行完一个阶段的所有任务

      • 执行 nextTick 队列里的内容

      • 执行 微任务队列 的内容

    • Node v10 之后

      • 和浏览器行为统一
  • 代码

    • await 描述的函数相当于放进 new Promise 里面,后面的语句放入 .then 里面

    • async 的函数要被调用才会触发

    async function async1() {
      console.log('async1 start')
      await async2()
      console.log('async1 end')
    }
    async function async2() {
      console.log('async2')
    }
    console.log('script start')
    setTimeout(function () {
      console.log('setTimeout')
    }, 0)
    async1()
    new Promise(function (resolve) {
      console.log('promise1')
      resolve()
    }).then(function () {
      console.log('promise2')
    })
    console.log('script end')
    
    // script start
    // async1 start
    // async2
    // promise1
    // script end
    // async1 end
    // promise2
    // setTimeout
    
    
    • promise 没有 resolve 的时候,它的 .then 不会被添加到微任务队列中,还在宏任务

    • children6 放到了 .then 的 res 里面,被 resolve 延迟执行

    image.png