乐趣区

Vue原理NextTick-源码版-之-宏微任务的抉择

写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue 版本 【2.5.17】

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面 关注公众号 也可以吧

【Vue 原理】NextTick – 源码版 之 宏微任务的抉择

nextTick 已经写了三篇文章啦,这是最后一篇源码版,没看过的童鞋可以看看白话版简单了解下拉

【Vue 原理】NextTick – 白话版 简单了解下 NextTick

在前面的文章 NextTick- 源码版之独立自身 中

埋下过两个问题

1、Vue 在哪里使用到了 宏任务和 微任务

2、Vue 为什么需要 宏任务 和微任务

今天的任务就是解决这两个问题!!!

在这里,大家肯定必须一定要了解了 宏任务和 微任务的哈,这两个东西不赘述了

首先,第一个问题就是宏微任务的使用场景场景


宏微任务的使用场景

1、Vue 一般情况下使用的是微任务

2、在绑定 DOM 事件的时候,会使用宏任务。

这么讲,有点笼统,准确地说,应该是

事件回调执行过程中,在 JS 主线程为空之后,异步代码执行之前,所有通过 nextTick 注册的异步代码都是用宏任务。

来看看绑定 DOM 事件的源码

通过 addEventListener 给 DOM 绑定事件

function add$1(event, handler) {handler = withMacroTask(handler);
    target$1.addEventListener(event, handler);

}



function withMacroTask(fn) {return fn._withTask || (fn._withTask = function() {

        useMacroTask = true;        

        var res = fn.apply(null, arguments);

        useMacroTask = false;        

        return res

    })
}

你看到了,把原先 DOM 事件的回调包装了一遍,然后通过设置 useMacroTask 来控制注册宏任务

useMacroTask 没见过,在 nextTick 独立流程中已经讲过了的

在调用 nextTick 的时候,正是通过这个变量来控制,此次异步代码注册的任务类型

Vue.nextTick =function (cb, ctx) {callbacks.push(function() {cb && cb.call(ctx);

    });    



    if (!pending) {

        pending = true;        

        if (useMacroTask) {macroTimerFunc();
        } else {microTimerFunc();
        }
    }
}

好多,现在我们来解决第二个问题!


为什么需要宏微任务

为什么要特地在事件回调执行期间 使用宏任务啊,想了好好久啊,才脑抽想到去看了下 Vue 的注释

大概意思是这样

本来 Vue 是从来都使用微任务的,因为微任务的优先级比较高,执行比较快。但是同时也是因为这样导致了一个问题

什么问题?

在连续事件发生的期间,微任务就已经执行了

就是

事件回调执行完成之后,会马上执行微任务

那么连续多个事件回调同时执行,就会导致连续多次执行微任务

如果连续多个事件回调中,都有修改数据,如下

this.state = xxxxx

那么很明显,会导致页面频繁的更新,这显然不是我们想要的结果

那到底什么是连续的事件?

那就是冒泡!

我们来现场演示一下微任务下的冒泡事件

<div style="height:100px;width:100px;background:red">
  <div style="height:60px;width:60x;background:black">
    <div style="height:30px;width:30px;background:blue">
    </div>

  </div>

</div>
div1.onclick = function() {console.log("div1");

    Promise.resolve().then(() = >{console.log("promise1")

    })
}

div2.onclick = function() {console.log("div2");    

    Promise.resolve().then(() = >{console.log("promise2")

    })
}

div3.onclick = function() {console.log("div3");    

    Promise.resolve().then(() = >{console.log("promise3")

    })
}

看到了吗,promise 在一个事件回调结束之后马上就调用了

如果在 Vue 中的事件回调中修改了数据 this.state = xxxxx

然后数据一更改,就会注册微任务用于响应更新,然后事件结束之后,马上执行微任务

如果三个事件回调都有修改数据,那么就会注册三次,执行三次,就会更新三次

所以

尤大想到了一个方法,就是在事件回调执行时,注册的是宏任务

宏任务并不会在事件结束之后马上调用

只会在连续事件结束之后,才调用,这就是我们想要的

所以你才能看到 使用 useMacroTask 来控制注册的任务类型

现在我把上面的例子中的 promise 换成 setTimeout,重新点击一下

但是!!

在【Vue 2.6】中,我们已经看不到 useMacroTask 的身影了,为什么?

因为 Vue 又全部使用微任务了 …….. 天道轮回 …..

(其实并不是全部是微任务,兼容写法最后是 setTimeout)

你问,那冒泡又怎么办?

好吧,尤大想到了另一个办法来解决冒泡的问题

就是判断当时的 事件 target,来判断是否执行事件回调

也就间接解决了这个问题,看看新的绑定事件的源码

function add$1(name, handler) {handler = function(e) {        

        if (

            e.target === e.currentTarget ||

            e.target.ownerDocument !== document

        ) {return  handler.apply(this, arguments)

        }
    };
    target$1.addEventListener(name, handler);
}

通过判断 target 就解决了冒泡,但是这样就不能用冒泡了好像??

也不知道有没有什么坏处,如果有的话,后面尤大肯定会更新的

退出移动版