Vue 中为什么要有nextTick?
摘要:本文将浅析nextTick的作用、使用场景和背后的原理实现,希望对大家有所帮助。
本文分享自华为云社区《Vue 中的 nextTick 有什么作用?》,作者:CoderBin。
一、什么是nextTick
先看看官方对其的定义:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
什么意思呢?
我们可以理解成,Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新
举个例子:
Html结构
<div id="app"> {{ message }} </div>
构建一个vue实例
const vm = new Vue({ el: '#app', data: { message: '原始值' } })
修改message
this.message = '修改后的值1' this.message = '修改后的值2' this.message = '修改后的值3'
这时候想获取页面最新的DOM节点,却发现获取到的是旧值
console.log(vm.$el.textContent) // 原始值
这是因为message数据在发现变化的时候,vue并不会立刻去更新Dom,而是将修改数据的操作放在了一个异步操作队列中
如果我们一直修改相同数据,异步操作队列还会进行去重
等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿来进行处理,进行DOM的更新
为什么要有nexttick
举个例子
{{num}} for(let i=0; i<100000; i++){ num = i }
如果没有 nextTick 更新机制,那么 num 每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图),有了nextTick机制,只需要更新一次,所以nextTick本质是一种优化策略
二、使用场景
如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()
- 第一个参数为:回调函数(可以获取最近的DOM结构)
- 第二个参数为:执行函数上下文
// 修改数据 vm.message = '修改后的值' // DOM 还没有更新 console.log(vm.$el.textContent) // 原始的值 Vue.nextTick(function () { // DOM 更新了 console.log(vm.$el.textContent) // 修改后的值 })
组件内使用 vm.$nextTick() 实例方法只需要通过this.$nextTick(),并且回调函数中的 this 将自动绑定到当前的 Vue 实例上
this.message = '修改后的值' console.log(this.$el.textContent) // => '原始的值' this.$nextTick(function () { console.log(this.$el.textContent) // => '修改后的值' })
$nextTick() 会返回一个 Promise 对象,可以是用async/await完成相同作用的事情
this.message = '修改后的值' console.log(this.$el.textContent) // => '原始的值' await this.$nextTick() console.log(this.$el.textContent) // => '修改后的值'
三、实现原理
源码位置:/src/core/util/next-tick.js
callbacks也就是异步操作队列
callbacks新增回调函数后又执行了timerFunc函数,pending是用来标识同一个时间只能执行一次
export function nextTick(cb?: Function, ctx?: Object) { let _resolve; // cb 回调函数会经统一处理压入 callbacks 数组 callbacks.push(() => { if (cb) { // 给 cb 回调函数执行加上了 try-catch 错误处理 try { cb.call(ctx); } catch (e) { handleError(e, ctx, 'nextTick'); } } else if (_resolve) { _resolve(ctx); } }); // 执行异步延迟函数 timerFunc if (!pending) { pending = true; timerFunc(); } // 当 nextTick 没有传入函数参数的时候,返回一个 Promise 化的调用 if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve; }); } }
timerFunc函数定义,这里是根据当前环境支持什么方法则确定调用哪个,分别有:
Promise.then、MutationObserver、setImmediate、setTimeout
通过上面任意一种方法,进行降级操作
export let isUsingMicroTask = false if (typeof Promise !== 'undefined' && isNative(Promise)) { //判断1:是否原生支持Promise const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { //判断2:是否原生支持MutationObserver let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { //判断3:是否原生支持setImmediate timerFunc = () => { setImmediate(flushCallbacks) } } else { //判断4:上面都不行,直接用setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0) } }
无论是微任务还是宏任务,都会放到flushCallbacks使用
这里将callbacks里面的函数复制一份,同时callbacks置空
依次执行callbacks里面的函数
function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
四、最后总结
- 把回调函数放入callbacks等待执行
- 将执行函数放到微任务或者宏任务中
- 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
AMD 已发布 Zen 4“Znver4” 的 GCC 基础支持代码
在上个月推出 Ryzen 7000 系列“Zen 4”处理器之后,AMD 发布了代码补丁,为 Zen 4 CPU 提供了 GNU 编译器集合 (GCC) 的初始支持。 但 Zen 4 CPU 支持的成本计算、调整与 znver3 保持相同,几乎没有什么用于指令延迟差异等优化的补丁。从补丁中可见, PTA_ZNVER3 直接转为PTA_ZNVER4 支持。 目前,基础 Znver4 的 GCC 支持代码可在邮件列表中找到,有希望将其纳入 GCC 13 主线中,然后在明年 4 月左右发布。 就在 AMD 发布 Zen 4 for GCC 的同一周,英特尔推出了 Meteor Lake 和 Sierra Forest 的 GCC 补丁。据外媒 Phoronix 报道,英特尔通常会提前数年将其新 CPU 的编译器支持添加到 GCC(和 Clang)中,尤其是在添加主要 ISA 的情况下。
- 下一篇
ConcurrentHashMap源码,看我这篇就够了
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情 思考:HashTable是线程安全的,为什么不推荐使用? HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占,相当于所有线程进行读写时都去竞争一把锁,导致效率非常低下。 1 ConcurrentHashMap 1.7 在JDK1.7中ConcurrentHashMap采用了数组+分段锁的方式实现。 Segment(分段锁)-减少锁的粒度 ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。 1.存储结构 Java 7 版本 ConcurrentHashMap 的存储结构如图: ConcurrnetHashMap 由很多个 Segment 组合,而每一个 Segment 是一个类似于 HashMap 的结构,所以每一个 Ha...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- Mario游戏-低调大师作品
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7安装Docker,走上虚拟化容器引擎之路