• 技术文章 >web前端 >Vue.js

    干货分享,带你了解Vue中的Vue.nextTick

    青灯夜游青灯夜游2022-03-22 11:38:00转载83
    本篇文章给大家分享一下Vue纯干货,介绍一下你不知道的Vue.nextTick,希望对大家有所帮助!

    用过Vue的朋友多多少少都知道$nextTick~ 在正式讲解nextTick之前,我想你应该清楚知道 Vue 在更新 DOM 时是异步执行的,因为接下来讲解过程会结合组件更新一起讲~ 事不宜迟,我们直进主题吧(本文以v2.6.14版本的Vue源码进行讲解)【相关推荐:vuejs视频教程

    一、nextTick小测试

    你真的了解nextTick吗?来,直接上题~

    <template>
      <div id="app">
        <p ref="name">{{ name }}</p>
        <button @click="handleClick">修改name</button>
      </div>
    </template>
    
    <script>
      export default {
      name: 'App',
      data () {
        return {
          name: '井柏然'
        }
      },
      mounted() {
        console.log('mounted', this.$refs.name.innerText)
      },
      methods: {
        handleClick () {
          this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText))
          this.name = 'jngboran'
          console.log('sync log', this.$refs.name.innerText)
          this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText))
        }
      }
    }
    </script>

    请问上述代码中,当点击按钮“修改name”时,'nextTick1''sync log''nextTick2'对应的this.$refs.name.innerText分别会输出什么?注意,这里打印的是DOM的innerText~(文章结尾处会贴出答案)

    如果此时的你有非常坚定的答案,那你可以不用继续往下看了~但如果你对自己的答案有所顾虑,那不如跟着我,接着往下看。相信你看完,不需要看到答案都能有个肯定的答案了~!


    二、nextTick源码实现

    源码位于core/util/next-tick中。可以将其分为4个部分来看,直接上代码

    1. 全局变量

    callbacks队列、pending状态

    const callbacks = [] // 存放cb的队列
    let pending = false // 是否马上遍历队列,执行cb的标志

    2. flushCallbacks

    遍历callbacks执行每个cb

    function flushCallbacks () {
      pending = false // 注意这里,一旦执行,pending马上被重置为false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]() // 执行每个cb
      }
    }

    3. nextTick的异步实现

    根据执行环境的支持程度采用不同的异步实现策略

    let timerFunc // nextTick异步实现fn
    
    if (typeof Promise !== 'undefined' && isNative(Promise)) {
      // Promise方案
      const p = Promise.resolve()
      timerFunc = () => {
        p.then(flushCallbacks) // 将flushCallbacks包装进Promise.then中
      }
      isUsingMicroTask = true
    } else if (!isIE && typeof MutationObserver !== 'undefined' && (
      isNative(MutationObserver) ||
      MutationObserver.toString() === '[object MutationObserverConstructor]'
    )) {
      // MutationObserver方案
      let counter = 1
      const observer = new MutationObserver(flushCallbacks) // 将flushCallbacks作为观测变化的cb
      const textNode = document.createTextNode(String(counter)) // 创建文本节点
      // 观测文本节点变化
      observer.observe(textNode, {
        characterData: true
      })
      // timerFunc改变文本节点的data,以触发观测的回调flushCallbacks
      timerFunc = () => { 
        counter = (counter + 1) % 2
        textNode.data = String(counter)
      }
      isUsingMicroTask = true
    } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
      // setImmediate方案
      timerFunc = () => {
        setImmediate(flushCallbacks)
      }
    } else {
      // 最终降级方案setTimeout
      timerFunc = () => {
        setTimeout(flushCallbacks, 0)
      }
    }

    4. nextTick方法实现

    cbPromise方式

    export function nextTick (cb?: Function, ctx?: Object) {
      let _resolve
      // 往全局的callbacks队列中添加cb
      callbacks.push(() => {
        if (cb) {
          try {
            cb.call(ctx)
          } catch (e) {
            handleError(e, ctx, 'nextTick')
          }
        } else if (_resolve) {
          // 这里是支持Promise的写法
          _resolve(ctx)
        }
      })
      if (!pending) {
        pending = true
        // 执行timerFunc,在下一个Tick中执行callbacks中的所有cb
        timerFunc()
      }
      // 对Promise的实现,这也是我们使用时可以写成nextTick.then的原因
      if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
      }
    }

    案例1,同一轮Tick中执行2次$nextTicktimerFunc只会被执行一次

    this.$nextTick(() => console.log('nextTick1'))
    this.$nextTick(() => console.log('nextTick2'))

    2.png


    三、Vue组件的异步更新

    这里如果有对Vue组件化派发更新不是十分了解的朋友,可以先戳这里,看图解Vue响应式原理了解下Vue组件化和派发更新的相关内容再回来看噢~

    Vue的异步更新DOM其实也是使用nextTick来实现的,跟我们平时使用的$nextTick其实是同一个~

    这里我们回顾一下,当我们改变一个属性值的时候会发生什么?

    3.png

    根据上图派发更新过程,我们从watcher.update开时讲起,以渲染Watcher为例,进入到queueWatcher

    1. queueWatcher做了什么?

    // 用来存放Wathcer的队列。注意,不要跟nextTick的callbacks搞混了,都是队列,但用处不同~
    const queue: Array<Watcher> = []
    
    function queueWatcher (watcher: Watcher) {
      const id = watcher.id // 拿到Wathcer的id,这个id每个watcher都有且全局唯一
      if (has[id] == null) {
        // 避免添加重复wathcer,这也是异步渲染的优化做法
        has[id] = true
        if (!flushing) {
          queue.push(watcher)
        }
        if (!waiting) {
          waiting = true
          // 这里把flushSchedulerQueue推进nextTick的callbacks队列中
          nextTick(flushSchedulerQueue)
        }
      }
    }

    2. flushSchedulerQueue做了什么?

    function flushSchedulerQueue () {
      currentFlushTimestamp = getNow()
      flushing = true
      let watcher, id
      // 排序保证先父后子执行更新,保证userWatcher在渲染Watcher前
      queue.sort((a, b) => a.id - b.id)
      // 遍历所有的需要派发更新的Watcher执行更新
      for (index = 0; index < queue.length; index++) {
        watcher = queue[index]
        id = watcher.id
        has[id] = null
        // 真正执行派发更新,render -> update -> patch
        watcher.run()
      }
    }

    4.png


    四、回归题目本身

    相信经过上文对nextTick源码的剖析,我们已经揭开它神秘的面纱了。这时的你一定可以坚定地把答案说出来了~话不多说,我们一起核实下,看看是不是如你所想!

    1、如图所示,mounted时候的innerText是“井柏然”的中文

    5.png

    2、接下来是点击按钮后,打印结果如图所示

    6.png

    7.png


    写在最后,nextTick其实在Vue中也算是比较核心的一个东西了。因为贯穿整个Vue应用的组件化、响应式的派发更新与其息息相关~深入理解nextTick的背后实现原理,不仅能让你在面试的时候一展风采,更能让你在日常开发工作中,少走弯路少踩坑!好了,本文到这里就暂告一段落了,如果读完能让你有所收获,就帮忙点个赞吧~画图不易、创作艰辛鸭~

    (学习视频分享:vuejs教程web前端

    以上就是干货分享,带你了解Vue中的Vue.nextTick的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:Vue Vue.nextTick
    上一篇:什么是Mixin?带你了解Vue中的Mixin混入 下一篇:vue中replace的用法是什么
    PHP编程就业班

    相关文章推荐

    • 总结分享10道经典vue面试题,快来做做看!• 宝藏项目!分享一款开箱即用的Vue3组件库:Varlet• VSCode插件分享:一个实时预览Vue/React组件的插件• 怎么上手Vue全新状态管理Pinia,看看这篇文章吧!• 如何快速上手vue3,学会这几个API吧!• 什么是Mixin?带你了解Vue中的Mixin混入

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网