通过microtask和macrotask理解Vue.nextTick()的实现

一丶JavaScript 运行机制详解:再谈Event Loop

   1.JavaScript 运行机制详解:

      JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

    单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

   JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

    于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行 

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

  注:只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制

   任务队列:

    "任务队列"中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列",等待主线程读取。"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。

    2.Event Loop
     主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)

  3.   任务队列中的microtask和macrotask

  任务队列(task queue)中的异步任务分为两种:微任务(microtask)宏任务(macrotask)

      宏任务(macrotask): 在浏览器端,其可以理解为该任务执行完后,在下一个macrotask执行开始前,浏览器可以进行页面渲染。触发macrotask任务的操作包括:

           setTimeoutsetIntervalsetImmediateI/OUI renderingmacro task事件:

      备注:宏任务一般是当前事件循环的最后一个任务,ui的渲染在这里最后执行,浏览器的ui绘制会插在每个macrotask之间,这就是为什么angularjs中settimeout会触发视图更新的                   原因。阻塞macrotask会导致ui数据不能更新

      这里注意:script(整体代码)即一开始在主执行栈中的同步代码本质上也属于macrotask,属于第一个执行的task

      微任务microtask可以理解为页面渲染前立即执行的任务,值得注意的是,UI Rendering是在micro-task之后执行。给micro-task队列添加过多回调阻塞macro-task队列的任务        执行是小事,重点是这有可能会阻塞UI Render,导致页面不能更新。浏览器也会基于性能方面的考虑,对micro-task中的任务个数进行限制。触发microtask任务的操作包括:

    Promises(浏览器实现的原生Promise)MutationObserverprocess.nextTick

      备注:事件冒泡甚至会在microtask中的任务执行之后,microtask优先级非常高

      Macrotasks、Microtasks执行机制:

      1.主线程执行完后会先到micro-task队列中读取可执行任务

      2.主线程执行micro-task任务

      3.主线程到macro-task任务队列中读取可执行任务

      4.主线程执行macro-task任务

      5....转到Step 1

二丶vue.nextTick实现
      在 Vue.js 里是数据驱动视图变化,由于 JS 执行是单线程的,在一个 tick (task)的过程中,它可能会多次修改数据,但 Vue.js 并不会傻到每修改一次数据就去驱动一次视图变化,它会把这些数据的修改全部 push 到一个队列里,然后内部调用 一次 nextTick 去更新视图,所以数据到 DOM 视图的变化是需要在下一个 tick 才能完成。这便是我们为什么需要 vue.nextTick.
     在每个 task 运行完以后,UI 都会重渲染,那么很容易想到在 microtask 中就完成数据更新(数据更新并不代表这ui更新),当前 task 结束就可以得到最新的 UI 了。反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。
vue.nextTick:
     定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。(修改数据之后Ui不会立即触发更新)

     理解:nextTick(),是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当数据更新了,在dom中渲染后,自动执行该函数.

     使用场景:

     1.1、Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。

     1.2、当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作都需要放进Vue.nextTick()的回调函数中;通俗的理解是:更改数据后当你想立即使用js操作新的视图的时候需要使用它

     1.3、在使用某个第三方插件时 ,希望在vue生成的某些dom动态发生变化时重新应用该插件,也会用到该方法,这时候就需要在 $nextTick 的回调函数中执行重新应用插件的方法。

猜你喜欢

转载自www.cnblogs.com/8080zh/p/12627988.html