关于宏任务和微任务

1.宏任务和微任务是什么?

(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行);microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前

JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,Event Loop 的方案应用而生。根据规范,事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务。

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 在此次 tick 中选择最先进入队列的任务(oldest task),如果有则执行(一次)

  • 检查是否存在 Microtasks,如果存在则不停地执行,直至清空 Microtasks Queue

  • 更新 render

  • 主线程重复执行上述步骤

在上诉tick的基础上需要了解几点:

  • JS分为同步任务和异步任务

  • 同步任务都在主线程上执行,形成一个执行栈

  • 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。

  • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

2.哪些是宏任务和微任务?

宏任务包括:

script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)

微任务包括:

Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)

3.宏任务和微任务的执行方式

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)

  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中

  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)

  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染

  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

我们通过一个流程图能更好的解释:

宏微任务执行机制.png

4.通过一个示例检测理论

//面试题:请问如下代码在控制台的输出顺序是什么?
console.log('start')

setTimeout(() => {
  console.log('setTimeout')
}, 0)

new Promise((resolve) => {
  console.log('promise')
  resolve()
})
  .then(() => {
    console.log('then1')
  })
  .then(() => {
    console.log('then2')
  })

console.log('end')
//控制台输出结果
start
promise
end
then1
then2
setTimeout

解析:

代码自上向下执行,先输出start;然后遇到延时器,将其放入宏任务列表作为宏任务1;继续执行代码到promise了,输出里面的promise;然后就是promise跟着的两个then,这两位是微任务,放入微任务列表中作为微任务1和微任务2;继续执行代码到最后输出end;到这里同步代码全部执行完毕,现在开始调取微任务,按照顺序执行输出then1和then2;到这里微任务执行完毕;现在开始调取宏任务,也就是延时器,输出setTimeout

5.DOM渲染和script的执行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        console.log('你好,世界!')
    </script>
</head>
<body>
    <h1>Hello,world!</h1>
</body>
</html>

如上代码由于script在head里面,因此会先执行js输出“你好,世界!”,然后才会取渲染页面在页面展示h1标题“Hello,world!”;如果script是一个耗时很久的循环甚至是个死循环,则页面的展示会受到影响,因此如果在head中的js里写入获取节点的方法是获取不到的~

在实际的使用中,我们会使用js和axois访问数据取渲染页面;axios访问是个异步函数;因此如果再写一个同步函数获取你渲染出来的节点也是获取不到的~因此我们的决解方案大概有两个:

  1. 将获取节点和调用给节点绑定事件的函数写在axios的then里面,且在渲染后面,这样作为then中同级别的同步代码,按顺序执行时能够读取到节点的

  2. 将获取节点和调用给节点绑定事件的函数写在一个setTimeout函数中,因为是宏任务,所以会在微任务渲染之后执行,也可以获取到节点~但是实际测试结果是,至少要给延时器设定个100ms左右的延时,才会生效,我不写或者写个10ms的时候,发现还是获取不到节点,这一点我也很费解,待我问到原因会补充上;

6.使用宏任务延时器实现进度条效果

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 500px;
        }

        #hd {
            width: 500px;
            height: 50px;
            background-color: rgb(55, 236, 55);
            text-align: center;
            line-height: 50px;
            font-size: 25px;
        }
    </style>
</head>

<body>
    <div>
        <div id="hd"></div>
    </div>

    <script>
        let hd = document.querySelector('#hd');

        function handle() {
            let i = 0;
            //每次函数自调用都会新建一个20ms延时的run,反复迭代完成一个宏任务就新建一个宏任务
            //因此实际效果解释每20ms会将进度条div的宽度增加1%实现进度条的效果
            //所有的js和css都在页面中,可以cv去看一下效果
            (function run() {
                hd.innerHTML = `${i}%`;
                hd.style.width = i + '%';
                if (++i <= 100) {
                    setTimeout(run, 20);
                }
            })();
        }
        handle();
    </script>
</body>

</html>

结尾:宏任务和微任务的执行机制较为复杂,这篇文章也只是皮毛,感谢b站后盾人老师的讲解视频,待后续我深入后将会继续更新;宏任务和微任务的执行机制和效率在大型项目中会体现明显,因此学习道路还很长,文中如有错误,请各位大大不吝赐教~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容