文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Vue $nextTick能获取到最新Dom的原因是什么

2023-07-04 11:54

关注

这篇文章主要介绍“Vue $nextTick能获取到最新Dom的原因是什么”,在日常操作中,相信很多人在Vue $nextTick能获取到最新Dom的原因是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue $nextTick能获取到最新Dom的原因是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

正文

<template>    <p id='text'>{{text}}</p>    <button @click='change'>click</button></template><script>    export default {        data() {            return {                text: 'hello world'            }        }        methods: {            change() {                this.text = 'hello girl'                const textElement = document.getElementById('text')                console.log(textElement.innerHTML)            }        }    }</script>

相信会用 Vue 的同学们应该都知道,这里的 change 方法里面打印的 textElement.innerHTML 的值还是 hello world,并不是修改之后的 hello girl,如果想要输出的是修改后的是 hello girl,就需要使用 $nextTick,像这样

this.text = 'hello girl'await this.$nextTick()const textElement = document.getElementById('text')console.log(textElement.innerHTML) // hello girl// 或者这样this.$nextTick(() => {    const textElement = document.getElementById('text')    console.log(textElement.innerHTML) // hello girl})

这样就可以输出 hello girl 了。

那么,为什么用了 $nextTick 就可以了呢,Vue 在背后做了哪些处理,接下来本文将从 Vue 的源码深入了解 $nextTick 背后的原理。

修改数据之后

在看源码之前,先来搞明白一个问题,为什么我们在修改数据之后,并没有拿到最新的 dom 呢?

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

以上是 vue 官网上给出的解释,第一句话是重点,解答了上面提出的那个问题,因为 dom 更新是异步的,但是我们的代码却是同步执行的,也就是说数据改变之后,dom 不是同步改变的,所以我们不能直接拿到最新的 dom。下面就从源码里来看 dom 是何时更新的,以及我们为什么用了 $nextTick 就可以拿到最新的 dom。

首先这个问题的起因是数据改变了,所以我们就直接从数据改变之后的代码看

function defineReactive() {    // src/core/observer/index.js    // ...    Object.defineProperty(obj, key, {      enumerable: true,      configurable: true,      get: function reactiveGetter () {        const value = getter ? getter.call(obj) : val        if (Dep.target) {          dep.depend()          if (childOb) {            childOb.dep.depend()            if (Array.isArray(value)) {              dependArray(value)            }          }        }        return value      },      set: function reactiveSetter (newVal) {        const value = getter ? getter.call(obj) : val                if (newVal === value || (newVal !== newVal && value !== value)) {          return        }                if (process.env.NODE_ENV !== 'production' && customSetter) {          customSetter()        }        // #7981: for accessor properties without setter        if (getter && !setter) return        if (setter) {          setter.call(obj, newVal)        } else {          val = newVal        }        childOb = !shallow && observe(newVal)        dep.notify()      }    })    // ...}

这个方法就是用来做响应式的,多余的代码删了一些,这里只看这个 Object.defineProperty,数据改变之后会触发 set,然后 set 里面,中间的一大堆都不看,看最后一行 dep.notify(),这个就是用来数据改变之后发布通知的,观察者模式嘛,都懂的哈,然后就接着来看这个 notify 方法里面做了什么,不用再找这个 dep 了,直接快捷键跳转函数定义,嗖一下,很快的

// src/core/observer/dep.jsclass Dep {  static target: ?Watcher;  id: number;  subs: Array<Watcher>;  constructor () {    this.id = uid++    this.subs = []  }  // ...  notify () {    // stabilize the subscriber list first    const subs = this.subs.slice()    if (process.env.NODE_ENV !== 'production' && !config.async) {      subs.sort((a, b) => a.id - b.id)    }    for (let i = 0, l = subs.length; i < l; i++) {      subs[i].update()    }  }}

update 方法

这个 Dep 类就是用来收集响应式依赖并且发布通知的,看 notify 方法,遍历了所有的依赖,并且挨个触发了他们的 update 方法,接着再看 update 方法

export default class Watcher {  // src/core/observer/watcher.js  // ...  update () {        if (this.lazy) {      this.dirty = true    } else if (this.sync) {      this.run()    } else {      queueWatcher(this)    }  }  // ... }

这个 Watcher 类可以理解为就是一个侦听 器或者说是观察者,每个响应式数据都会对应的有一个 watcher 实例,当数据改变之后,就会通知到它,上面那个 Dep 收集的就是他,看里面的这个 update 方法,我们没用 lazysync,所以进来之后执行的是那个 queueWatcher 方法,

function queueWatcher (watcher: Watcher) {  const id = watcher.id  if (has[id] == null) {    has[id] = true    if (!flushing) {      queue.push(watcher)    } else {      let i = queue.length - 1      while (i > index && queue[i].id > watcher.id) {        i--      }      queue.splice(i + 1, 0, watcher)    }    // queue the flush    if (!waiting) {      waiting = true      if (process.env.NODE_ENV !== 'production' && !config.async) {        flushSchedulerQueue()        return      }      nextTick(flushSchedulerQueue)    }  }}

可以看到这个方法接收的是一个 watcher 实例,方法里面首先判断了传进来的 watcher 是否已经传过了,忽略重复触发的 watcher,没有传过就把它 push 到队列中,然后下面看注释也知道是要更新队列,把一个 flushSchedulerQueue 方法传到了 nextTick 方法里面,这个 flushSchedulerQueue 方法里面大概就是更新 dom 的逻辑了,再接着看 nextTick 方法里面是怎么执行传进去的这个更新方法的

nextTick 方法里面怎么执行传进去更新方法

// src/core/util/next-tick.jsexport function nextTick (cb?: Function, ctx?: Object) {  let _resolve  callbacks.push(() => {    if (cb) {      try {        cb.call(ctx)      } catch (e) {        handleError(e, ctx, 'nextTick')      }    } else if (_resolve) {      _resolve(ctx)    }  })  if (!pending) {    pending = true    timerFunc()  }  // $flow-disable-line  if (!cb && typeof Promise !== 'undefined') {    return new Promise(resolve => {      _resolve = resolve    })  }}

我们在外面调用的 $nextTick 方法其实就是这个方法了,方法里面先把传进来的 callback 存起来,然后下面又执行了一个 timerFunc 方法,看下这个 timerFunc 的定义

if (typeof Promise !== 'undefined' && isNative(Promise)) {  const p = Promise.resolve()  timerFunc = () => {    p.then(flushCallbacks)    if (isIOS) setTimeout(noop)  }  isUsingMicroTask = true} else if (!isIE && typeof MutationObserver !== 'undefined' && (  isNative(MutationObserver) ||  // PhantomJS and iOS 7.x  MutationObserver.toString() === '[object MutationObserverConstructor]')) {  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(flushCallbacks)  }} else {  // Fallback to setTimeout.  timerFunc = () => {    setTimeout(flushCallbacks, 0)  }}function flushCallbacks () {  pending = false  const copies = callbacks.slice(0)  callbacks.length = 0  for (let i = 0; i < copies.length; i++) {    copies[i]()  }}

这一堆代码就是异步处理更新队列的逻辑了,在下一个的事件循环“tick”中,去刷新队列,依次尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境都不支持,则会采用 setTimeout(fn, 0) 代替。

然后再回到最初的问题,为什么用了 $nextTick 就可以获取到最新的 dom 了 ?

我们再来梳理一遍上面从数据变更到 dom 更新之前的整个流程

然后这时你调用了 $nextTick 方法,传进来一个获取最新 dom 的回调,这个回调也会推到那个数组 callbacks 中,此时遍历 callbacks 并执行所有回调的动作已经放到了异步队列中,到这(假设你后面没有其他的代码了)所有的同步代码就执行完了,然后开始执行异步队列中的任务,更新 dom 的方法是最先被推进去的,所以就先执行,你传进来的获取最新 dom 的回调是最后传进来的所以最后执行,显而易见,当执行到你的回调的时候,前面更新 dom 的动作都已经完成了,所以现在你的回调就能获取到最新的 dom 了。

到此,关于“Vue $nextTick能获取到最新Dom的原因是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯