最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    正文概述 掘金(橙某人)   2021-07-09   481

    概括

    我们常说 Javascript (下面简称JS)是一门单线程编程语言,而单线程就意味着同一时刻只允许一个代码段在主线程上执行,那么对于执行一些需要长时间等待的任务来说,它们会占据线程不放,这会造成后续代码无法执行,程序无法正常使用。这是单线程的弊端,而 JS 是通过事件循环机制(Event Loop)来解决这一弊端。

    要理解清楚事件循环这个机制会涉及很多让人头疼的概念,如 JS 的执行机制、调用栈(执行栈)、任务队列(消息队列)、宏任务与微任务。当然,认识完这些东西,你对 JS 将会有更深层的认识,话不多说,我们开始本篇的愉快旅程吧。

    栈、堆、队列

    在讲正题之前,我们先来了解一下三个数据结构类型,相信很多人也不是很陌生了,之所以讲这个,是因为下面可能会涉及这其中的概念,希望你能有个更好的认识。

    • 栈(Stack):栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。栈被称为是一种后入先出(LIFO,last-in-first-out)的数据结构。由于栈具有后入先出的特点,所以任何不在栈顶的元素都无法访问。为了得到栈底的元素,必须先拿掉上面的元素。

    Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    • 队列(Queue):栈数据结构的访问规则是LIFO(后进先出),而队列数据结构的访问规则是FIFO(Fist-In-First-Out,先进先出)。队列在列表的末端添加项,从列表的前端移除项。

    Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    • 堆(Heap):堆是一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。由于堆的这个特性,常用来实现优先队列,堆的存取是随意,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,我们只需要关心书的名字。

    Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    JS执行机制

    我们知道 JS 的执行顺序是从上到下一行一行顺序执行的,但是如果在执行过程中碰到耗时比较长的任务要怎么办呢? JS 就只能傻等了吗? 当然没那么傻了,聪明的程序猿把任务分成了同步任务和异步任务,避免了单线程的 JS 在执行过程被阻塞的问题,下面我画了个草图来描述这一执行过程:

    Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    图解:

    • JS 在开始执行的时候,会把任务分为同步任务和异步任务。

    • 同步任务会直接进入主线程依次执行。

    • 异步任务会进入到 “任务队列” 中,等待异步任务有了结果后,会将注册的回调函数放入任务队列中等待,当主线程空闲的时候(执行栈被清空),会被读取到执行栈等待主线程的执行。

    • 异步任务可以再分为宏任务和微任务。

    宏任务与微任务

    JS 把异步任务再分为宏任务和微任务。那么,它们俩又是什么呢?

    • 宏任务(Macrotask):可以理解为每次执行栈执行的代码就是一个宏任务。(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
    • 微任务(Microtask):可以理解为当前宏任务执行结束后立即执行的任务。也就是说,在当前宏任务后,下一个宏任务之前。微任务是在运行宏任务/同步任务的时候产生的,是属于当前任务的。

    为什么会产生宏任务和微任务呢?
    前面我们说过,异步任务会进入所谓的 “任务队列” 中了,而任务队列具有 队列 的性质,先进先出,也就是后加入的任务必须等待前面的任务执行完才能执行。如果在执行的过程中突然有重要的数据需要获取,或是说有事件突然需要处理一下,按照队列的先进先出顺序这些是无法得到及时处理的。这个时候就催生了宏任务和微任务,微任务使得一些异步任务得到及时的处理。

    举个例子形容宏任务和微任务?
    曾经看到的一个例子很好,宏任务和微任务形象的来说就是:你去营业厅办一个业务会有一个排队号码,当叫到你的号码的时候你去窗口办充值业务(宏任务执行),在你办理充值的时候你又想改个套餐(微任务),这个时候工作人员会直接帮你办,不可能让你重新排队。例子来源

    产生宏任务和微任务分别有哪些?

    • 宏任务:

      1. script(整体的代码)
      2. setTimeout
      3. setInterval
      4. I/O 操作
      5. UI渲染
      6. setImmediate (Node.js 环境)
    • 微任务:

      1. Promise.then
      2. Mutation Observer API (具体使用)
      3. Process.nextTick(Node独有)
      4. Object.observe (废弃)

    微任务与任务的区别?

    1. 首先,每当一个任务存在,事件循环都会检查该任务是否正把控制权交给其他 JavaScript 代码。如若不然,事件循环就会运行微任务队列中的所有微任务。接下来微任务循环会在事件循环的每次迭代中被处理多次,包括处理完事件和其他回调之后。

    2. 其次,如果一个微任务通过调用 queueMicrotask(), 向队列中加入了更多的微任务,则那些新加入的微任务 会早于下一个任务运行 。这是因为事件循环会持续调用微任务直至队列中没有留存的,即使是在有更多微任务持续被加入的情况下。

    事件循环

    了解了 JS 的整个执行机制过程后,事件循环(Event Loop)就比较简单好理解了,开头我们提过它的出现是为了解决 JS 单线程带来的弊端,它也是整个 JS 单线程执行过程中最核心的一部分,也是最重要的一部分。

    讲事件循环前,还要涉及一个 执行栈(也称调用栈) 的概念。它又是什么呢?网上的说法是,所有同步任务都在主线程上执行,形成一个执行栈。详细解释

    还是一样,我们先上图再分析:

    Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    1. 所有同步任务都在主线程上执行,形成一个执行栈。
    2. 主线程之外,还存在一个 “任务队列”,它是存放异步任务运行后的回调函数的,也就是异步任务有了运行结果,就在"任务队列"之中放置一个事件。
    3. 一旦 “执行栈” 中的所有同步任务执行完毕,主线程就会读取 “任务队列”,看看里面有哪些事件。然后把那些对应的异步任务,压入执行栈中,开始执行。

    而主线程不断重复上面的第三步,就形成了我们常说的事件循环了。

    举个栗子

    讲了那么多,举个栗子最实在,下面我们就来细细分析一下。

    var p = new Promise((resolve, reject) => {
        console.log('Promise - 初始化');
        resolve('Promise - 结果')
    })
    
    function fn1() {
        console.log('fn1 - 执行');
    }
    
    function fn2() {
        console.log('fn2 - 开始执行');
        setTimeout(() => {
            console.log('setTimeout - 执行');
        })
        fn1();
        console.log('fn2 - 再次执行');
        p.then(res => {
            console.log('Promise - 第一个then :' + res);
        }).then(() => {
            console.log('Promise - 第二个then');
        })
    }
    
    fn2();
    
    1. 首先,从上到下依次执行,先是会把 Promise() 对象压入 执行栈 中执行,输出 “Promise - 初始化” 并给 p 赋值了一个 Promise 对象,之后 执行栈Promise() 对象弹出,也就是 执行栈 清空了。
    2. 继续往下,不管两个函数的声明,来到 fn2() 的调用,把 fn2() 压入栈中执行,输出 “fn2 - 开始执行”,继续把 setTimeout()压入栈中执行,会把它里面的 console.log('setTimeout - 执行'); 语句放入 任务队列中,弹出 setTimeout()执行栈fn2() 继续调用。
    3. 往下,来到 fn1() 的调用,把 fn1() 压入栈中执行,输出 “fn1 - 执行”,弹出 fn1(),往下,再次打印输出 “fn2 - 再次执行”
    4. 往下,来到第一个 .then(),把它压入栈中执行,会把它里面的 console.log('Promise - 第一个then :' + res); 语句放入 微任务队列 中,弹出它,再压入第二个 .then() ,继续把 console.log('Promise - 第二个then'); 语句放入 微任务队列 中, 弹出它。
    5. 到这里, fn2() 就执行完了,会被 执行栈 弹出,栈内又清空了。
    6. 同步任务都执行完了,主线程空闲了,开始读取 微任务队列,按照队列先进先出的性质,会先把 console.log('Promise - 第一个then :' + res); 语句压入 执行栈 中执行,输出 “Promise - 第一个then :Promise - 结果” ,然后弹出,再压入另一语句,输出 “Promise - 第二个then” 弹出。
    7. 执行栈 又清空了,开始读取 任务队列,把 console.log('setTimeout - 执行'); 语句压入栈中执行,输出 “setTimeout - 执行”,然后弹出。

    这就是整个执行过程了,文字有点多和乱,但仔细看应该能瞧明白的(-^〇^-) ,步骤中加黑文字对应下图的输出。

    Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    这上面的例子应该还比较好理解,但它还不是很能体现 Event Loop 的精髓,我们再来改造改造。

    ...
    function fn2() {
        console.log('fn2 - 开始执行');
        setTimeout(() => {
            console.log('setTimeout - 执行');
            // start
            setTimeout(() => {
                console.log('又一个宏任务')
            })
            p.then(() => {
                console.log('Promise - 第三个then')
            })
            // end
        })
        fn1();
        console.log('fn2 - 再次执行');
        p.then(res => {
            console.log('Promise - 第一个then :' + res);
        }).then(() => {
            console.log('Promise - 第二个then')
        })
    }
    
    fn2();
    

    上面代码我们在一个宏任务中再增加了一个宏任务和一个微任务,然后我们直接来看输出结果:

    Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    是否符合你的预期呢?这里其实有个容易踩坑的点,既然 .then() 是一个微任务,我们新加的微任务,为什么它没有在上面提到的第 6 步骤中读取 微任务队列 的时候一起执行呢?原因很简单,就是下面红框的宏任务还没有执行。我们应该把它们看成一个整体,它们还没有细化。

    Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    之所以来细说这个点,主要是想表明,执行一个宏任务可能会继续产生宏任务和微任务,然后主线程来继续读取 微任务队列任务队列,以此来构成 Event Loop 的过程。



    至此,本篇文章就写完啦,撒花撒花。

    Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
    老样子,点赞+评论=你会了,收藏=你精通了。


    下载网 » Javascript执行机制 - 宏任务与微任务、事件循环(Event Loop)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元