javascript知识梳理(四):运行时-宏任务与微任务

本文深入探讨JavaScript中的宏任务和微任务,包括它们的定义、为何需要微任务、微任务(Promise)在异步编程中的应用及其解决的编码问题,帮助读者理解JavaScript异步执行机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

本篇文章的目的是解释清楚事件循环中的宏任务与微任务,还是三个问题着手:

  1. 什么是宏任务与微任务?
  2. 为什么需要微任务?
  3. 微任务(promise)的应用?

二、什么是宏任务与微任务?

宏任务

主线程任务队列中的任务:

  1. 加载的js文件执行
  2. 各种交互事件
  3. 浏览器渲染事件
  4. setTimeout,setInterval定时器
  5. I/O线程加入的事件

微任务

主线程运行时,调用栈中维护的队列:

  1. promise
  2. MutationObserver

代码示例:

const promise = new Promise(resolve => {
  console.log('promise start')
  console.log('add setTimeout1')
  setTimeout(_ => {
      console.log('setTimeout1 start')
      resolve()
    }, 0)
  console.log('add setTimeout2')
  setTimeout(_ => console.log('setTimeout2 start'), 0)
  console.log('promise end')
})
promise.then(res => {
  console.log('promise.then')
})
promise start
/*  
add setTimeout1
add setTimeout2
promise end
setTimeout1 start
promise.then
setTimeout2 start 
*/

运行过程示意图
运行过程示意图

由上可见,微任务的执行时机是在当前宏任务执行结束之前执行,所以并不会受到事件队列的影响。这保证了微任务的实时性优势。

三、为什么需要微任务?

ES6之前,javascript语言并不能创建真正意义上的异步任务,必须借用宿主API实现类似功能,如setTimeout。
随着前端技术的发展,对于异步回调的功能需求越来越多,导致出现了各种各样实现方式,缺乏统一标准。这对于一门语言来说,不利于长期发展。所以,ES6提供了Promise对象
传统的宏任务异步任务,性能不高。例如,创建一个宏任务的异步任务,在执行开始到被创建之间这段时间,有可能会触发一些其他事件。这将导致本应该作为下一个事件执行的异步任务,却必须要等待前面任务执行完才能执行。这将导致该任务实时性变得不可靠。
在代码执行中,经常需要监听dom节点的变化。因为这种需求有两个特征。第一:变化频率高。第二:实时性要求高。基于以上特征,引出了三种处理方式:
处理方式api优点缺点
暴力轮询setTimeout, setInterval简单粗暴实时性低,不好把控时间间隔,性能低
同步回调Mutation Event实时性高,观察者模式回调,不会额外创建任务每一次dom变化都会触发,增删过程很容易触发无用回调
异步微任务处理MutationObserver实时性高,等待当前任务中,所有节点变换完成后才执行,不会执行无用回调实时性没有同步任务高,需要浏览器支持

四、微任务(promise)的应用

微任务的出现

微任务的出现,最初是为了处理监听dom变化的性能问题,也就是MutationObserver。而Promise的出现,其实主要是解决一些异步编码风格的问题(李冰老师)。

promise解决了什么异步编码风格的问题?

  • 将处理多状态结果的函数封装成只返回成功与失败两种状态,解决编码状态处理混乱的问题。如以下代码:
    // 最简单最常用的请求事件
    function ajax_get(url) {
      return new Promise((resolve, reject) => {
        const request = new XMLHttpRequest()
        request.onreadystatechange = function() {
          if (request.readyState === 4) {
            // 请求成功状态
            if (request.status === 200) resolve(request.response)
            else reject('错误')
          }
        }
        request.onerror = e => reject(e)
        request.ontimeout = e => reject(e)
        request.open('GET', url, true)
        request.send()
      })
    }
    // 借用菜鸟教程的请求地址
    const promise = ajax_get('https://www.runoob.com/try/ajax/ajax_info.txt')
    promise.then(res => {
      console.log(res)
    }, error => console.log(error))
    

    以上代码中,我们关心的只有request.readyState = 4且request.status = 200时的成功返回,而其他多种状态都视为请求失败。通过Promise函数,可以很好的处理该种场景。当然,还有很多种方式可以实习,而Promise属于官方给出的处理办法,所以更具有安全跟权威性。

  • 解决回调地狱,让代码更线性,增加代码的可读性以及可维护性,如下
    // 等待第一个请求完成后,调用第二个请求
    const promise1 = ajax_get('https://www.runoob.com/try/ajax/ajax_info.txt')
    const promise2 = promise1.then(res => {
      console.log('第一次请求结果:', res)
      return ajax_get('https://www.runoob.com/try/ajax/ajax_info.txt')
    }, error => console.log(error))
    promise2.then(res => {
      console.log('第二次请求结果:', res)
    }, error => console.log(error))
    
    如上所示,
    1. promise将函数的结果,保存在一个对象中,具有保存状态的功能。可以等待将来的任一时刻,再对结果进行处理。这种方式有效的消除了同步回调处理的执行顺序问题。
    2. promise将函数的结果,从层层回调中返回出来。使得代码层次更清晰,免除了作用域限制,使得回调地狱的场景得到完美的解决。

综上所述,promise的出现,更多的是为了实现语言风格的统一,并且根据用户的实际需求,解决现有代码的不足。

个人理解,如有不实之处,欢迎指出,也欢迎一起学习进步!。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值