解析$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也会降低代码的可读性,所以大家还是"按需加载"的好~
作者:京东保险 卓雅倩
来源:京东云开发者社区 转载请注明来源

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Databend 源码阅读: Meta-service 数据结构
作者:张炎泼(XP) Databend Labs 成员,Databend 分布式研发负责人 https://github.com/drmingdrmer 引言 Databend 是一款开源的云原生数据库,采用 Rust 语言开发,专为云原生数据仓库的需求而设计。 面向云架构:Databend 是完全面向云架构的数据库,可以在云环境中灵活部署和扩展 简介 | Databend 内幕大揭秘。 弹性扩缩容能力:Databend 提供秒级的弹性扩缩容能力,可以根据需求快速增加或减少计算资源。 存算分离:Databend 实现了存储和计算的分离,可以独立增加计算节点而无需进行数据迁移,提高了计算资源的利用效率。 共享存储:Databend 使用共享存储,可以方便地接入各种数据源,并支持 SQL 查询。 弹性多租户隔离:Databend 的 Meta Service 层提供了弹性的多租户隔离的服务,可以满足不同用户的需求 Databend:新一代云原生数仓的架构与展望 - 知乎。 它的主数据以对象存储的形式存储在云端,而关键的元数据则托管在专用的 Meta Service 中。 本文旨在介绍 Me...
- 下一篇
测试用例设计方法六脉神剑——第六剑:心法至简,百家之长集成
1 引言 在前面几篇文章中,为大家介绍的都是系统的方法论,但在实际需求测试的过程当中,受到外部环境及业务逻辑的影响,比如涉及多需求耦合、浏览器缓存堆积等情况,仅针对当前需求设计出的测试用例就会有覆盖不全的问题,此时就需要借助以往的经验进行反向错误推测,辅助其他方法对测试用例进行完善。在本篇文章中,首先会对错误推测法的思路进行介绍,并对本系列文章中讲解的所有测试用例设计方法进行归纳总结,给出具体的可应用业务场景,便于大家在遇到同类场景时可快速筛选出适用的方法,将测试用例设计方法论真正落地到日常工作中。 2 错误推测法 2.1 定义 是基于经验和直觉推测程序中所有可能存在的各种错误, 从而有针对性的设计测试用例的方法。 2.2 设计思路 • 总结归纳以往的测试版本,找出共通的易错点 • 借助网络搜索,参考网上的测试设计要点 • 站在用户的角度去考虑非常规操作 • 编写测试场景标准库来完善错误推测方法 2.3 总结 方法 错误推测法 优点 ① 充分发挥人的直觉和经验 ② 集思广益 ③ 方便使用 ④ 快速切入 缺点 ① 难以知道测试的覆盖率 ② 可能丢失大量未知的区域 ③ 带有主观性且难以...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- 2048小游戏-低调大师作品
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS关闭SELinux安全模块