浏览器的事件循环

157 阅读4分钟

为什么js在浏览器中会有事件循环的机制?

  • js是单线程的
    • JavaScript的主要用途是与用户互动,以及操作DOM。如果它是多线程的会有很多复杂的问题 要处理,比如有两个线程同时操作DOM,一个线程删除了当前的DOM节点,一个线程是要操 作当前的DOM阶段,最后以哪个线程的操作为准?为了避免这种,所以JS是单线程的。即使 H5提出了web worker标准,它有很多限制,受主线程控制,是主线程的子线程。
  • event loop 实现非堵塞

事件循环中两种任务

  • 宏任务
    • 整体代码、setTimeout、setInterval、I/O操作、UI 渲染等
  • 微任务
    • new Promise().then后面的回调、MutaionObserver(监听事件的改变,前端的回溯)、

为什么要引入微任务的概念,只有宏任务不行吗?

  • 宏任务 先进先出的原则执行
    • 在js的执行过程中(页面的渲染过程中),他们所有的任务保持着先进先出的原则
    • ⻚面渲染事件,各种IO的完成事件等随时被添加到任务队列中,一直会保持先进先出的原则执 行,我们不能准确地控制这些事件被添加到任务队列中的位置。但是这个时候突然有高优先级 的任务需要尽快执行,那么一种类型的任务就不合适了,所以引入了微任务队列。
    • 在一个宏任务执行完一遍之后,会把他中途中碰到的微任务已经添加到队列里面去,宏任务执行完之后,去微任务的队列里面,把已经添加进去的微任务执行一遍,在微任务队列里面又碰到新添加的微任务,就继续把它执行,直到结束执行下一个宏任务。

Node中的事件循环和浏览器的时间循环有什么区别?

  • timers定时器
    • 执行已经安排的setTimeout和 setInterval的回调函数
  • pending callback 待定回调
    • 执行延迟到下一个循环迭代的I/O回调
  • idle,prepare
    • 仅系统内部使用
  • poll
    • 检索新的I/o事件,执行I/o相关的回调
  • check
    • 执行setImmediate()回调函数
  • close callback
    • socket.on('close',()=>{})

微任务和宏任务在node中的执行顺序

  • Node v10及以前
    • 执行完一个阶段中所有任务
    • 执行nextTick队列里的任务
    • 执行完微任务队列的任务
  • Node v10以后
    • 和浏览器的行为统一了

例题1

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,reject){
    console.log('promise1')
    resolve()
}).then(function(){
    console.log('promise2')
})
console.log('script end')
答案:
1.script start
2.async1 start
3.async2
4.promise1
5.script end
6.async1 end
7.promise2
8.setTimeout
  1. 运行整体代码--> 打印script start
  2. 遇到setTimeout,不执行,放到下一个宏任务执行
  3. 运行async1函数
    • 首先打印--> async1 start
    • 执行awati async2函数--> async2
      • 可以把await理解成new Promise,await后面的放在.then后面执行
      new Promise(function(resolve){
          async2()
      }).then(function(){
          async1 end
      })
      
      • 所以把 console.log('async1 end') 放在第一个微任务里,一会执行
  4. 执行new Promise中
    • --> 打印 promise1
    • resolve返回成功,把.then后面的内容再放入第二个微任务里,一会儿执行
  5. 运行代码 --> 打印 script end
  6. 整体代码中第一次鞥宏任务执行完毕,开始执行微任务
  7. 首先运行第一个碰到的微任务--> 打印出 async1 end
  8. 运行第二个碰到的微任务 --> 打印出 promise2
  9. 宏任务里的微任务全部运行完毕,开始执行setTimeout -->打印出 setTimeout

例题2

console.log('start')
setTimeout(function(){//1
    console.log('children2')
    Promise.resolve().then(()=>{  //一
        console.log('children3')
    })
},0)
new Promise(function(resolve,reject){
    console.log('children4')
    setTimeout(function(){//2
        console.log('children5')
        resolve('children6')
    })
}).then(res=>{//二
    console.log('children7')
    setTimeout(()=>{
        console.log(res)
    })
})
答案:
// start
// children4
// children2
// children3
// children5
// children7
// children6
  1. 运行整体代码 --> start
  2. 遇到setTimeout放入下一轮宏任务中
  3. 运行到new Promise 打印--> children4
  4. 又遇到setTimeout,放入下下轮宏任务中
  5. 尝试清空微任务,发现没有微任务
    • 注意:.then是在setTimeout之后resolve的,所以.then的回调这个微任务现在还没有触发
  6. 清空第一个宏任务 打印-->children2
  7. 尝试清空这一轮宏任务中的微任务:Promise.resolve.then(),打印-->children3
  8. 继续清空第二个宏任务,resolve有返回值了 打印-->children5
  9. 尝试清空微任务,打印-->children7
  10. 打印resolve的返回值 打印-->children6

例题3

const p = function(){
    return new Promise((resolve,reject)=>{
        const p1 = new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve(1)
            },0)
            resolve(2) 
        })
        p1.then((res)=>{
            console.log(res)
        })
        console.log(3)
        resolve(4)
    })
}

p().then(res=>{ //第一个微任务
    console.log(res)
})
console.log('end')
答案:
// 3
// end
// 2
// 4
  1. 观察题目,p返回了一个promise,.then后面的等待p有返回之后执行,先放入微任务中
  2. 打印-->3
  3. 打印-->end
  4. p1中返回resolve(2),setTimeout中就不返回了,所以执行微任务.then打印-->2
  5. 打印-->4