重学前端——事件循环

事件循环

js是单线程的 为什么要有事件循环机制哩?假设js只是单线程的 碰到同步任务还好,按照顺序依次执行,如果异步任务那种耗时很长的任务,比如发请求,那么这个js任务就卡住了 只能等到请求返回成功才能执行,这样效率就太低了。事件循环机制是为了解决这样的问题,让同步任务能跳过异步任务 并发的执行,等到异步任务完成后回调放入任务队列等待主线程调用执行。

  • 同步任务:在执行栈中按照先进后出的顺序执行,一个执行完了 再轮到下一个执行

  • 异步任务:不阻碍主线程的任务执行

  • 栈 先进后出,可以想像成一头被堵住的管道,只能从一头放入拿出,那先放入的压在下面,只能等上面的拿出后才能拿出。 函数调用时形成调用栈 当调用一个函数时 被压入栈中,函数执行完从栈中移除,执行权交给下一个函数。

  • 堆 对象被分配在堆中,堆是一个大块内存存储区域

  • 队列 先进先出,可以想象成两头通畅的水管,一头流入那一头流出。 js 运行时包含一个待处理消息的消息队列,每个消息都关联一个回调

    Js 任务类型:

    • 宏任务:script代码 setTimeout setInterval setImediate I/O UIRendering
    • 微任务: Process.nextTick Promise Mutation.Observer

    js代码在执行时 都有一个执行栈,按照先进后出的顺序执行,同步任务执行完从执行栈中弹出,异步任务会把回调放在任务队列中,在异步完成后,判断执行栈是否为空,如果为空会把任务队列中的任务放入执行栈,开启一次事件循环。任务队列中的任务分为两种,微任务和宏任务,先宏后微,第一次整体js代码作为一个宏任务,遇到宏任务就放任务队列,遇到微任务放任务队列,当执行栈空了就从任务队列拿微任务进行处理,执行完所有的微任务。当所有微任务完成,就开启下一轮事件循环,执行宏任务,宏任务执行完执行微任务。

    下面例子,执行过程:

    console.log('console start');
    setTimeout(function() {
        console.log('eventLoop2-宏任务1:setTimeout1');
        new Promise(function(resolve) {
            console.log('promise2');
            resolve()
        }).then(function() {
            console.log('eventLoop2-微任务1:then2');
        }).then(function() {
            console.log('eventLoop2-微任务2:then22');
        })
    })
    setTimeout(function() {
        console.log('eventLoop3-宏任务1:setTimeout2');
    })
    
    new Promise(function(resolve) {
        console.log('promise');
        resolve()
    }).then(function() {
        console.log('eventLoop1-微任务1:then');
    }).then(function() {
        console.log('eventLoop1-微任务2:then1');
    })
    
    console.log('console end');
    
    
    // console start
    // promise
    // console end
    // eventLoop1-微任务1:then
    // eventLoop1-微任务2:then1
    // eventLoop2-宏任务1:setTimeout1
    // promise2
    // eventLoop2-微任务1:then2
    // eventLoop2-微任务2:then22
    // eventLoop3-宏任务1:setTimeout2
    
    
    1. 同步代码放入执行栈 执行后弹出 输出 console start。整体代码作为宏任务放入宏任务队列

    2. 接着setTimeout放入执行栈 执行后弹出,setTime callback (eventLoop2-宏任务1) 放入宏任务任务队列

    3. 接着setTimeout放入执行栈 执行后弹出,setTime callback(eventLoop3-宏任务1)放入宏任务任务队列

    4. new Promise 放入执行栈执行后弹出,输出 promisepromise.then callback(eventLoop1-微任务1)放入微任务队列 promise.then.then callback(eventLoop1-微任务2)放入微任务队列

    5. 执行同步代码 输出console end 此时执行栈为空

    6. 开启第一次事件循环,从宏任务队列对头那任务,执行整体宏任务,询问微任务是否为空

    7. 微任务队列不为空执行微任务队列,promise.then callback(eventLoop1-微任务1)放入执行栈执行完退出,输出 eventLoop1-微任务1:then

    8. 继续询问微任务队列是否为空 不为空 promise.then.then callback(eventLoop1-微任务2)放入执行栈 执行完退出 输出 eventLoop1-微任务2:then

    9. 继续询问微任务队列是否为空 为空。开启下一轮事件循环

    10. 拿宏任务执行,把setTime callback (eventLoop2-宏任务1)放入执行栈 执行完退出,打印eventLoop2-宏任务1:setTimeout1,继续执行new Promise 输出primise2,把 promise2的回调eventLoop2-微任务1:then2eventLoop2-微任务2:then22放入微任务队列

    11. 宏任务执行完 继续询问微任务 ,执行微任务 打印 eventLoop2-微任务1:then2eventLoop2-微任务2:then22

    12. 微任务队列为空 开启第三轮事件循环 执行setTime callback(eventLoop3-宏任务1) 打印eventLoop3-宏任务1:setTimeout2

    setTimeout(()=> {},0) 不是立即执行 而是等到执行栈为空再执行

    setTimeout(()=> {},1000) 不是1秒后执行 1秒只是最快执行时间

事件循环和浏览器渲染

并不是每一次事件循环都伴随着一次浏览器渲染,由屏幕刷新频率 页面性能 等共同决定。浏览器会尽可能保证帧率的稳定,如果无法维持60fps会降低到30fps 。

  • requestAnimationFrame 在重新渲染前调用

  • setTimeout 浏览器会把几次任务合并

    setTimeout(() => {
            console.log("sto")
            requestAnimationFrame(() => console.log("rAF"))
    })
    setTimeout(() => {
      console.log("sto")
      requestAnimationFrame(() => console.log("rAF"))
    })
    
    queueMicrotask(() => console.log("mic"))
    queueMicrotask(() => console.log("mic"))
    
  • requestIdleCallback 空闲调度算法 在屏幕渲染后执行,把一些计算量较大但没那么紧急的任务放到空闲时间去执行 不要影响浏览器中优先级高的任务。

你可能感兴趣的:(重学前端,前端,javascript,vue.js)