解析$nextTick魔力,为啥大家都爱它?
1.为什么需要使用$nextTick?
首先我们来看看官方对于$nextTick的定义:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
由于vue的试图渲染是异步的,生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM其实并未进行渲染,而此时进行DOM操作是徒劳的,所以一定要将DOM操作的js代码放到Vue.nextTick()的回调函数中。除了在created()钩子函数中使用之外咱们还会遇到很多种需要使用到Vue.nextTick()的场景,如下所示:
咱们日常生活中常常会遇上上述场景,当我们点击按钮更新数据时候,如下示例:
<template>
<div>
<input type="text" v-if = "isShow" ref="input"/>
<button @click="handleClick">点击显示输入框,并且获取输入框焦点</button>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false
}
},
methods : {
handleClick () {
this.isShow = true
this.$refs.input.focus() //控制栏会报错,因为还没有这个dom
}
}
}
</script>
点击控制栏显示效果:控制栏报错,提示没有获取到dom元素;

所以现在Vue.nextTick()派上了用场,Vue.nextTick() 方法的作用正是等待上一次事件循环执行完毕,并在下一次事件循环开始时再执行回调函数。这样可以保证回调函数中的 DOM 操作已经被 Vue.js 进行过更新,从而避免了一些潜在的问题,如下代码所示:
<template>
<div>
<input type="text" v-if = "isShow" ref="input"/>
<button @click="handleClick">点击显示输入框,并且获取输入框焦点</button>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false
}
},
methods : {
handleClick () {
this.isShow = true
this.$nextTick(()=>{
this.$refs.input.focus()
})
}
}
}
</script>
加上this.$nextTick后就能够使得输入框获取到焦点;
总而言之Vue.nextTick()就是下次 DOM 更新渲染后执行延迟回调函数。在日常开发中,我们在修改数据之后使用这个方法,就可以获取更新后的 DOM的同时进行在对DOM进行相对应操作的 js代码;
2.$nextTick如何实现的?
JS是单线程执行的,所有的同步任务都是在主线程上执行的,形成了一个执行栈,从上到下依次执行,异步代码会放在任务队列里面。
•同步任务
在主线程里执行,当浏览器第一遍过滤html文件的时候可以执行完;(在当前作用域直接执行的所有内容,包括执行的方法、new出来的对象)
•异步任务
耗费时间较长或者性能较差的,浏览器执行到这些的时候会将其丢到异步任务队列中,不会立即执行
同时异步任务分为宏任务(如setTimeout、setInterval、postMessage、setImmediate等)和微任务(Promise、process.nextTick等),浏览器执行这两种任务的优先级不同;会优先执行微任务队列的代码,微任务队列清空之后再执行宏任务的队列,这样循环往复;
JS自上向下进行代码的编译执行,遇到同步代码压入JS执行栈执行后出栈,遇到异步代码放入任务队列,当JS执行栈清空,去执行异步队列中的回调函数,先去执行微任务队列,当微任务队列清空后,去检测执行宏任务队列中的回调函数,直至所有栈和队列清空
整体流程如下图所示:

接下来让我们看看nextTick的源码~
vue将nextTick的源码放在了vue/core/util/next-tick.js中。如下图所示:

我们把这个文件拆成三个部分来看:
1.nextTick定义函数
我们将nextTick函数单独拿出来,callbacks是一个回调队列,其实调用nextTick就是往这个数组里面传执行任务,callbacks新增回调函数之后执行timerFunc函数,pending是用来限制同一个事件循环内只能执行一次的pending锁;
const callbacks = [] // 回调队列
let pending = false //
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
// cb 回调函数会经统一处理压入 callbacks 数组
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// 执行异步延迟函数 timerFunc
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
// 当 nextTick 没有传入函数参数的时候,返回一个 Promise 化的调用
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
2.timerFunc函数 做了四个判断,先后尝试当前环境是否能够使用原生的Promise.then、MutationObserver和setImmediate,不断的降级处理,如果以上三个都不支持,则最后就会直接使用setTimeOut,主要操作就是将flushCallbacks中的函数放入微任务或者宏任务,等待下一个事件循环开始执行;宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务;
export let isUsingMicroTask = false
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
//是否支持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]'
)) {
//是否支持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)) {
timerFunc = () => {
//是否支持setImmediate
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
//上面都不行,直接使用setTimeout
setTimeout(flushCallbacks, 0)
}
}
3.flushCallbacks函数
flushCallbacks函数只有几行,也很好理解,将pending锁置为false,同时将callbacks数组复制一份之后再将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]()
}
}
值得注意的是,$nextTick 并不是一个真正意义上的微任务microtask,而是利用了事件循环机制来实现异步更新。因此,它的执行时机相对于微任务可能会有所延迟,但仍能保证在 DOM 更新后尽快执行回调函数。
总的来说,nextTick就是
1.将传入的回调函数放入callbacks数组等待执行,定义pending判断锁保证一个事件循环中只能调用一次timerFunc函数;
2.根据环境判断使用异步方式,调用timerFunc函数调用flushCallbacks函数依次执行callbacks中的回调函数;
3.个人小结
nextTick可避免数据更新后导致DOM的数据不一致的问题,提供了更稳定的异步更新机制,解决了created钩子函数DOM未渲染会造成的异步数据渲染问题,但如果过多的使用nextTick会导致事件循环中任务数量和回调函数增多,有可能出现可怕的回调地狱,导致性能下降,同时过度依赖nextTick也会降低代码的可读性,所以大家还是"按需加载"的好~
作者:京东保险 卓雅倩
来源:京东云开发者社区 转载请注明来源
解析$nextTick魔力,为啥大家都爱它?的更多相关文章
- Capslock+ 键盘党都爱的高效利器 - 让 Windows 快捷键操作更加灵活强大
Capslock+ 键盘党都爱的高效利器 - 让 Windows 快捷键操作更加灵活强大 优化辅助 Windows 2016-06-05 91,167 微博微信QQ空间印象有道邮件 ...
- NoSQL初探之人人都爱Redis:(1)Redis简介与简单安装
一.NoSQL的风生水起 1.1 后Web2.0时代的发展要求 随着互联网Web2.0网站的兴起,传统的关系数据库在应付Web2.0网站,特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经 ...
- NoSQL初探之人人都爱Redis:(2)Redis API与常用数据类型简介
一.Redis API For .Net 首先,不得不说Redis官方提供了众多的API开发包,但是目前Redis官方版本不支持.Net直接进行连接,需要使用一些第三方的开源类库.目前最流行的就是Se ...
- NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例
一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,“消息队列”是在消息的传输过程中保存消息的容器 ...
- NoSQL初探之人人都爱Redis:(4)Redis主从复制架构初步探索
一.主从复制架构简介 通过前面几篇的介绍中,我们都是在单机上使用Redis进行相关的实践操作,从本篇起,我们将初步探索一下Redis的集群,而集群中最经典的架构便是主从复制架构.那么,我们首先来了解一 ...
- 【转】 NoSQL初探之人人都爱Redis:(4)Redis主从复制架构初步探索
一.主从复制架构简介 通过前面几篇的介绍中,我们都是在单机上使用Redis进行相关的实践操作,从本篇起,我们将初步探索一下Redis的集群,而集群中最经典的架构便是主从复制架构.那么,我们首先来了解一 ...
- 【转】NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例
一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,“消息队列”是在消息的传输过程中保存消息的容器 ...
- 【转】NoSQL初探之人人都爱Redis:(2)Redis API与常用数据类型简介
一.Redis API For .Net 首先,不得不说Redis官方提供了众多的API开发包,但是目前Redis官方版本不支持.Net直接进行连接,需要使用一些第三方的开源类库.目前最流行的就是Se ...
- 【转】NoSQL初探之人人都爱Redis:(1)Redis简介与简单安装
一.NoSQL的风生水起 1.1 后Web2.0时代的发展要求 随着互联网Web2.0网站的兴起,传统的关系数据库在应付Web2.0网站,特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经 ...
- C#基础原理拾遗——面试都爱问的委托和事件(纠正)
这篇博客是我昨天写的,文中的观点有些问题,后经过网友留言和个人学习发现错误,原文还是保留,更改补在后面,不怕贻笑大方,唯恐误人子弟.不知道还能不能放在首页,让被误导的同学再被反误导一次. 一.原文 几 ...
随机推荐
- Journey -「CQOI 2021」
Day -1 Thu. & Fri. 恰逢学校运动会,于是向班主任申请了不去,然后就在机房坐着.不美好的事情可能就是文化课老师还留了这两天的作业,不过-> 一旦放弃了作业,什么都好说了呢 ...
- MySQL系列之MyCat——架构图、准备、安装、配置文件、垂直分表、MyCAT核心特性——分片(水平拆分)、范围分片、枚举分片、Mycat全局表、E-R分片
文章目录 1. MyCAT基础架构图 2. MyCAT基础架构准备 2.1 环境准备: 2.2 删除历史环境: 2.3 创建相关目录初始化数据 2.4 准备配置文件和启动脚本 2.5 修改权限,启动多 ...
- snowboy 无法 install
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple snowboy Looking in indexes: https://pypi.tun ...
- python3 gui 计时器
# -*- coding: utf-8 -*- # @Time : 2023/4/4 21:53 # @File : 定时器gui.py # @Software: PyCharm Community ...
- 教育法学期末考试02MOOC
期末考试 返回 期末考试试卷为客观题,总分为100分,占课程成绩的40%.其中包含16道单选题,2道多选题.共18道题.单选题每道5分,多选题每道10分,限时90分钟完成. 倒计时: 01:21: ...
- C#学习笔记--复杂数据类型、函数和结构体
C#基础 复杂数据类型 特点:多个数据变量地一个集合体,可以自己命名 种类:枚举.数组和结构体 枚举:整型常量的集合 数组:任意变量类型的顺序存储的数据集合 结构体:任意变量类型的数据组合成的数据块 ...
- 比赛总结:Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324)
比赛:Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324) A-same 1.常 ...
- [ABC208E] Digit Products 题解
Digit Products 题目大意 求有多少个不大于 \(n\) 的正整数,使得该正整数各位乘积不大于 \(k\). 思路分析 观察数据范围,首先考虑数位 DP. 考虑设计记忆化搜索函数 dfs( ...
- React项目中webpack的配置过程
初始化一个web项目 使用npm init -y 初始化一个项目 在项目目录下创建src, dist文件夹,创建webpack.config.js配置文件 然后在src文件夹下创建index.js, ...
- 51单片机控制w5500实现udp组播通信
51单片机控制w5500实现udp组播通信 在socket打开之前,向Sn_MR (Socket n 模式寄存器)寄存中写入 0x82(1000 0010),将W5500加入组播组 对Sn_DIPR ...