宏任务 和 微任务

宏任务:

当前调用栈执行的代码成为宏任务,(主代码块和定时器)也或者宿主环境提供的叫宏任务

这些任务包括:

  • 渲染事件
  • 用户交互事件(如鼠标点击、滚动页面、放大缩小等)
  • JavaScript 脚本执行事件;
  • 网络请求完成、文件读写完成事件

微任务:

当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调事件:promise.then,proness.nextTick等等。 由语言标准提供的叫微任务.

执行顺序:

在挂起任务的时候, JS 引擎会把任务按照类别分到两个队伍当中。 首先在宏任务(macrotask) 队伍中取出第一个任务,执行完毕后。取出 microtask 队列中的所有任务顺序执行;周而复始,循环。

总结上面:
可以举个例子:就像银行柜台办理业务,每个来办理业务的人就像一个一个宏任务,当前用户业务办理完成然后 接待下一个客户,就像开始了下一个宏任务一样。但是一个客户可能要办理多项业务(修改密码,存款,转账等)这些业务就像微任务,只有微任务执行完成,才能执行下一个宏任务(总不能一个客户业务没有办理完,就让他去重新取号排队,估计要打人了!!!)

setTimeout(function(){
    console.log('定时器开始啦')
});

new Promise(function(resolve){
    console.log('马上执行for循环啦');
    for(var i = 0; i < 10000; i++){
        i == 99 && resolve();
    }
}).then(function(){
    console.log('执行then函数啦')
});

console.log('代码执行结束');

//马上执行for循环啦
//代码执行结束
//执行then函数啦
//定时器开始啦
  • 这段代码作为宏任务,进入主线程。
  • 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。(注册过程与上同,下文不再描述)
  • 接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。
  • 遇到console.log(),立即执行。
  • 好啦,整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。
  • ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。
  • 结束

看下demo:

Promise 在前 setTimeout在后面

new Promise((resolve) => {
  console.log('外层宏事件2');
  resolve()
}).then(() => {
  console.log('微事件1');
}).then(()=>{
  console.log('微事件2')
})
console.log('外层宏事件1');
setTimeout(() => {
  //执行后 回调一个宏事件
  console.log('内层宏事件3')
}, 0)

// 执行结果:
外层宏事件2
外层宏事件1
微事件1
微事件2
内层宏事件3
  • Promise 在前,Promise.then则是具有代表性的微任务,所有会进入的异步都是指事件的回调。所以说 new Promise 在实例化的过程中所执行的代码都是同步进行的,而then 中的才是异步执行的
  • setTimeout就是作为宏任务来存在的
  • 在同步执行完成之后,检查是否有异步任务,微任务会在下一个宏任务前面全部完成
  • 所以 结果是 外2-外1-微1-微2-内3

setTimeout 在前面 Promise 在后面

setTimeout(() => {
  //执行后 回调一个宏事件
  console.log('内层宏事件3')
}, 0)
console.log('外层宏事件1');

new Promise((resolve) => {
  console.log('外层宏事件2');
  resolve()
}).then(() => {
  console.log('微事件1');
}).then(()=>{
  console.log('微事件2')
})
// 执行结果:
外层宏事件1
外层宏事件2
微事件1
微事件2
内层宏事件3
  • setTimeout 设定了时间,相当于取号了,在排队过程。
  • 然后在当前进程中又添加了一些Promise的处理(临时添加的业务)
  • 同步的外1 和外2 执行完成,开始执行微任务,微任务执行完成之后才执行下一个异步宏任务,所以结果如上;

前面提到宿主环境:能够使js 完美运行的环境,目前常见的环境就是两种宿主环境有浏览器和node

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
// 1-7 -6 -8 -2- 4- 3- 5- 9 -11- 10 -12

  • 整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
  • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
  • 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
  • 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
  • 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
  • 同步输出1-7 执行微任务 process1-then1 输出6 - 8
  • 好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:
    • 首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2。
    • 第二轮事件循环宏任务结束,我们发现有process2和then2两个微任务可以执行。 输出 3和5
    • 第二轮事件循环结束,第二轮输出2,4,3,5。
    • 第三轮事件循环开始,此时只剩setTimeout2了,执行。
    • 输出9,11,10,12。

node 环境和浏览器环境 又有什么区别呢?

  • 宏任务


    宏任务 和 微任务_第1张图片
    WechatIMG506.png
  • requestAnimationFrame姑且也算是宏任务吧,requestAnimationFrame在MDN的定义为,下次页面重绘前所执行的操作,而重绘也是作为宏任务的一个步骤来存在的,且该步骤晚于微任务的执行

  • 微任务

宏任务 和 微任务_第2张图片
WechatIMG507.png

JS 是单线程,所以同一个时间不能处理多个任务,所以每次办理完一个业务,都会询问当前客户是否还有其他要办理的业务(检查是否有未完成的微任务),当前用户办理完成,结束这个宏任务开始下一个宏任务,这样操作持续进行,而这样的操作就被称为Event Loop

什么是Event Loop?

event loop 顾名思义 就是事件循环。因为V8是单线程的,即同一时间只能干一件事情,但是呢文件的读取,网络的IO处理是很缓慢的,并且是不确定的,如果同步等待它们响应,那么用户就起飞了。于是我们就把这个事件加入到一个 事件队列里(task),等到事件完成时,event loop再执行另一个事件队列。

1、 update_time
在事件循环的开头,这一步的作用实际上是为了获取一下系统时间

2、timers
事件循环跑到这个阶段的时候,要检查是否有到期的timer,其实也就是setTimeout和setInterval这种类型的timer,到期了,就会执行他们的回调。

3、I/O callbacks
处理异步事件的回调,比如网络I/O,比如文件读取I/O。当这些I/O动作都结束的时候,在这个阶段会触发它们的回调。

4、idle, prepare
这个阶段内部做一些动作,与理解事件循环没啥关系

5、I/O poll阶段
这个阶段相当有意思,也是事件循环设计的一个有趣的点。这个阶段是选择运行的。选择运行的意思就是不一定会运行。

6、check
执行setImmediate操作

7、close callbacks
关闭I/O的动作,比如文件描述符的关闭,链接断开

除了task还有一个microtask,这一个概念是ES6提出Promise以后出现的。这个microtask queue只有一个。
并且会在且一定会在每一个task后执行,且执行是按顺序的。加入到microtask 的事件类型有Promise.resolve().then(), process.nextTick() 值得注意的是
event loop一定会在执行完micrtask以后才会寻找新的 可执行的task队列。而microtask事件内部又可以产生新的microtask

浏览器:

  • 宏任务(macroTask):script 中代码、setTimeout、setInterval、I/O、UI render
  • 微任务(microTask): Promise、Object.observe、MutationObserver。
  • I/O 有点笼统,点击个btn 上传一个文件,与程序交互的这些都可以称为I/O


const $inner = document.querySelector('#inner') const $outer = document.querySelector('#outer') function handler () { console.log('click') // 直接输出 Promise.resolve().then(_ => console.log('promise')) // 注册微任务 setTimeout(_ => console.log('timeout')) // 注册宏任务 requestAnimationFrame(_ => console.log('animationFrame')) // 注册宏任务 $outer.setAttribute('data-random', Math.random()) // DOM属性修改,触发微任务 } new MutationObserver(_ => { console.log('observer') }).observe($outer, { attributes: true }) $inner.addEventListener('click', handler) $outer.addEventListener('click', handler)

1、因为一次I/O创建了一个宏任务,也就是说在这次任务中会去触发handler
2、在同步的代码已经执行完以后,这时就会去查看是否有微任务可以执行,然后发现了Promise和MutationObserver两个微任务,遂执行之
3、click事件会冒泡,所以对应的这次I/O会触发两次handler函数(一次在inner、一次在outer),所以会优先执行冒泡的事件(早于其他的宏任务),也就是说会重复上述的逻辑
4、在执行完同步代码与微任务以后,这时继续向后查找有木有宏任务
5、因为我们触发了setAttribute,实际上修改了DOM的属性,这会导致页面的重绘,而这个set的操作是同步执行的,也就是说requestAnimationFrame的回调会早于setTimeout所执行

所以上面 执行顺序是:click -> promise -> observer -> click -> promise -> observer -> animationFrame -> animationFrame -> timeout -> timeout

node:

  • Node也是单线程,但是在处理Event Loop上与浏览器稍微有些不同
setImmediate与setTimeout的区别:

在官方文档中的定义,setImmediate为一次Event Loop执行完毕后调用。
setTimeout则是通过计算一个延迟时间后进行执行。

  • microTask:微任务;

  • nextTick:process.nextTick;

  • timers:执行满足条件的 setTimeout 、setInterval 回调;

  • I/O callbacks:是否有已完成的 I/O 操作的回调函数,来自上一轮的 poll 残留;

  • poll:等待还没完成的 I/O 事件,会因 timers 和超时时间等结束等待;

  • check:执行 setImmediate 的回调;

  • close callbacks:关闭所有的 closing handles ,一些 onclose 事件;
    idle/prepare 等等:可忽略。


  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick

进程和线程的区别:

  • 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
  • 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
  • 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
  • 调度和切换:线程上下文切换比进程上下文切换要快得多
  • 进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
  • 内存分配方面:
    系统在运行的时候会为每个进程分配不同的内存空间;
    而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
  • 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
  • 创建一个线程比进程开销小;
  • 线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)

浏览器都有哪些进程?

1.Browser进程(即上篇文章截图里面的浏览器进程):浏览器的主进程(负责协调、主控),只有一个。主要作用:

  • 负责浏览器界面显示,与用户交互。如前进,后退等
  • 负责各个页面的管理,创建和销毁其他进程
  • 将渲染(Renderer)进程得到的内存中的Bitmap(位图),绘制到用户界面上
  • 网络资源的管理,下载等

2、第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
3、GPU进程:最多一个,用于3D绘制等
4、浏览器渲染进程(即通常所说的浏览器内核)(Renderer进程,内部是多线程的):主要作用为页面渲染,脚本执行,事件处理等

浏览器内核

简单来说浏览器内核是通过取得页面内容、整理信息(应用CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。从上面我们可以知道,Chrome浏览器为每个tab页面单独启用进程,因此每个tab网页都有由其独立的渲染引擎实例

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成

  • GUI 渲染线程

GUI渲染线程负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,也就是说被”冻结”了.

  • JavaScript引擎线程

Javascript引擎,也可以称为JS内核,主要负责处理Javascript脚本程序,例如V8引擎。Javascript引擎线程理所当然是负责解析Javascript脚本

  • 定时触发器线程
  • 事件触发线程
  • 异步http请求线程

你可能感兴趣的:(宏任务 和 微任务)