nextTick原理解析

nextTick 是什么

$nextTick:根据官方文档的解释,它可以在 DOM 更新完毕之后执行一个回调函数,并返回一个 Promise(如果支持的话)

1// 修改数据 2vm.msg = "Hello"; 3 4// DOM 还没有更新 5Vue.nextTick(function() { 6 // DOM 更新了 7});

这块理解 EventLoop 的应该一看就懂,其实就是在下一次事件循环开始时开始更新 DOM,避免中间频繁的操作引起页面的重绘和回流。

这块引用官方文档:

可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。 这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

列如当设置vm.text = 'new value'时,该组件不会立即重新渲染,当刷新队列时,组件会在下一个事件循环‘tick’中更新,

1<div id="example">{{message}}</div> 2var vm = new Vue({ 3 el: '#example', 4 data: { 5 message: '123' 6 } 7}) 8vm.message = 'new message' // 更改数据 9vm.$el.textContent === 'new message' // false 10Vue.nextTick(function () { 11 vm.$el.textContent === 'new message' // true 12})

一般在设置了this.xx='xx'数据后,立即得到最新的 DOM 数据时,才会用到$nextTick,因为 DOM 的更新是异步进行的,所以获取需要用到这个方法。

更新流程(源码解析)

  1. 当数据被修改时,watcher 会侦听到变化,然后会将变化进行入队:
1/* 2 * Subscriber interface. 3 * Will be called when a dependency changes. 4 */ 5Watcher.prototype.update = function update() { 6 /* istanbul ignore else */ 7 if (this.lazy) { 8 this.dirty = true; 9 } else if (this.sync) { 10 this.run(); 11 } else { 12 queueWatcher(this); 13 } 14};
  1. 并使用 nextTick 方法添加一个 flushScheduleQueue 回调
1/** 2 * Push a watcher into the watcher queue. 3 * Jobs with duplicate IDs will be skipped unless it's 4 * pushed when the queue is being flushed. 5 */ 6function queueWatcher(watcher) { 7 var id = watcher.id; 8 if (has[id] == null) { 9 has[id] = true; 10 if (!flushing) { 11 queue.push(watcher); 12 } else { 13 // if already flushing, splice the watcher based on its id 14 // if already past its id, it will be run next immediately. 15 var i = queue.length - 1; 16 while (i > index && queue[i].id > watcher.id) { 17 i--; 18 } 19 queue.splice(i + 1, 0, watcher); 20 } 21 // queue the flush 22 if (!waiting) { 23 waiting = true; 24 25 if (!config.async) { 26 flushSchedulerQueue(); 27 return; 28 } 29 nextTick(flushSchedulerQueue); 30 } 31 } 32}
  1. flushScheduleQueue 加入到 callback 数组中,并且异步执行
1function nextTick(cb, ctx) { 2 var _resolve; 3 callbacks.push(function() { 4 if (cb) { 5 try { 6 cb.call(ctx); // !! cb 就是加入的回调 7 } catch (e) { 8 handleError(e, ctx, "nextTick"); 9 } 10 } else if (_resolve) { 11 _resolve(ctx); 12 } 13 }); 14 if (!pending) { 15 // 异步执行 操作 见timerFunc 16 pending = true; 17 timerFunc(); 18 } 19 // $flow-disable-line 20 if (!cb && typeof Promise !== "undefined") { 21 return new Promise(function(resolve) { 22 _resolve = resolve; 23 }); 24 } 25}
  1. timerFunc 操作就是异步执行了依次判断使用:Promise.then=>MutationObserver=>setImmediate=>setTimeout
1var timerFunc; 2 3if (typeof Promise !== "undefined" && isNative(Promise)) { 4 var p = Promise.resolve(); 5 timerFunc = function() { 6 p.then(flushCallbacks); 7 // 1. Promise.then 8 if (isIOS) { 9 setTimeout(noop); 10 } 11 }; 12 isUsingMicroTask = true; 13} else if ( 14 !isIE && 15 typeof MutationObserver !== "undefined" && 16 (isNative(MutationObserver) || 17 MutationObserver.toString() === "[object MutationObserverConstructor]") 18) { 19 // 2. MutationObserver 20 var counter = 1; 21 var observer = new MutationObserver(flushCallbacks); 22 var textNode = document.createTextNode(String(counter)); 23 observer.observe(textNode, { 24 characterData: true, 25 }); 26 timerFunc = function() { 27 counter = (counter + 1) % 2; 28 textNode.data = String(counter); 29 }; 30 isUsingMicroTask = true; 31} else if (typeof setImmediate !== "undefined" && isNative(setImmediate)) { 32 // 3. setImmediate 33 timerFunc = function() { 34 setImmediate(flushCallbacks); 35 }; 36} else { 37 //4. setTimeout 38 timerFunc = function() { 39 setTimeout(flushCallbacks, 0); 40 }; 41}
  1. flushCallbacks 遍历所有的 callbacks 并执行
1function flushCallbacks() { 2 pending = false; 3 var copies = callbacks.slice(0); 4 callbacks.length = 0; 5 for (var i = 0; i < copies.length; i++) { 6 copies[i](); 7 } 8}
  1. 其中就有前面加入的 flushScheduleQueue,利用 queue 中的 watcher 的 run 方法,更新组件
1for (index = 0; index < queue.length; index++) { 2 watcher = queue[index]; 3 watcher.run(); 4}

总结

以上就是 vue 的 nextTick 方法的实现原理了,总结一下就是:

  1. Vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行

  2. microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕

  3. 因为兼容性问题,vue 不得不做了 microtask 向 macrotask 的降级方案

参考

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

KVM调整cpu和内存

一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid