首页 > 其他分享 >解析$nextTick魔力,为啥大家都爱它?

解析$nextTick魔力,为啥大家都爱它?

时间:2023-12-20 12:12:09浏览次数:28  
标签:nextTick 解析 魔力 DOM callbacks 任务 执行 函数

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,解析,魔力,DOM,callbacks,任务,执行,函数
From: https://www.cnblogs.com/Jcloud/p/17916252.html

相关文章

  • 解析$nextTick魔力,为啥大家都爱它?
    1.为什么需要使用$nextTick?首先我们来看看官方对于$nextTick的定义:在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。由于vue的试图渲染是异步的,生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是......
  • 深度解析ArrayList:灵活高效的动态数组实现
    在Java集合框架中,ArrayList是一个常用而强大的类,它提供了动态数组的实现,允许在运行时动态调整数组的大小。ArrayList是List接口的实现类,基于动态数组的数据结构。它可以存储任意类型的对象,并提供了丰富的方法,包括添加、删除、遍历等,使其在各种场景下都能发挥重要作用。底层......
  • 实践解析HPA各关联组件扭转关系
    本文分享自华为云社区《HPA各关联组件扭转关系以及建议》,作者:可以交个朋友。一、背景应用程序的使用存在波峰波谷现象,在应用流量处于低谷期间,可以释放因过多的Pod而浪费的硬件资源。在应用流量高峰期提供弹性足够的Pod处理流量。二、HPA各个组件扭转关系kubernetes使用者通过......
  • Python 使用getopt解析命令行参数
    ​ 1、getopt模块此模块可协助脚本解析sys.argv中的命令行参数。它支持与Unixgetopt()函数相同的惯例(包括形式如'-'与'--'的参数的特殊含义)。也能通过可选的第三个参数来使用与GNU软件所支持形式相类似的长选项。1)getopt.getopt(args,shortopts,longopts=[])......
  • SSO单点登录源码解析
    单点登录:用户在单点登录成功后,会访问某一平台模块,1. 向客户端发送一个请求(免密登录接口),请求客户端接口会携带一个ticket,类似于:http//:ip:port:xxx?ticket=xxxx1. 这个请求会被客户端配置的EKPSSOCLIENT的过滤器拦截,读取配置文件sso-config.properties,获取过滤器链(CASURLFilter、Us......
  • 三道初三数学模拟几何综合题的解题思路解析
     ......
  • 深入解析 Python 中的对象创建与初始化:__new__ 与 __init__ 方法
    Python中的面向对象编程涉及许多特殊方法,其中__new__和__init__是两个关键的方法。它们分别负责对象的创建和对象的初始化,在对象的生命周期中扮演着不同而又互补的角色。让我们深入探讨这两个方法,了解它们的作用、区别以及如何在实际开发中应用。1. __new__方法当谈到Pyth......
  • Python中使用del删除列表元素的原理解析
    Python是一种功能强大的编程语言,提供了许多方便的操作列表的方法。其中,使用del关键字可以删除列表中的某个元素。本文将解析Python中使用del删除列表元素的原理,帮助您理解其工作原理和使用方法。1.列表是可变对象:在Python中,列表是一种可变对象,即可以在原地修改的对象。与不可变对象......
  • 羚通视频智能分析平台:车辆检测算法的深度解析
    随着科技的不断发展,视频监控技术已经深入到我们生活的各个角落。在这个背景下,羚通视频智能分析平台应运而生,它通过先进的车辆检测算法,为我们的生活带来了极大的便利。本文将详细介绍羚通视频智能分析平台的车辆检测算法。一、羚通视频智能分析平台简介羚通视频智能分析平......
  • Owasp Top10 漏洞解析 之注入
    一、注入漏洞是什么?注入漏洞,即将不受信任的数据作为命令或查询的一部分发送到解析器时,会产生诸如SQL注入NoSQL注入、OS注入和LDAP注入的注入缺陷。攻击者的恶意数据可以诱使解析器在没有适当授权的情况下执行非预期命今或访问数据。几乎任何数据源都能成为注入载体,包括环境变量......