• Vue2源码学习笔记 - 15.响应式原理—nextTick


    这一节继续研究 nextTick,$nextTick 也是差不多用法,很多小伙伴应该都用过,我们看下官方描述:

    vm.$nextTick( [callback] )
    $nextTick( [callback] )
    参数:
    {Function} [callback]
    用法:
    将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

    Vue.nextTick = nextTick;
    
    Vue.prototype.$nextTick = function (fn) {
      return nextTick(fn, this)
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Vue.nextTick 与 Vue.prototype.$nextTick 都是调用 nextTick 函数实现,我们直接看它的源代码:

    file: /src/core/util/next-tick.js

    import { noop } from 'shared/util'
    import { handleError } from './error'
    import { isIE, isIOS, isNative } from './env'
    
    export let isUsingMicroTask = false
    const callbacks = []
    let pending = false
    
    // microtask/macrotask 回调函数
    function flushCallbacks () {
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
    
    let timerFunc
    // 优先级 1 用 Promise 定义 microtask
    if (typeof Promise !== 'undefined' && isNative(Promise)) {
      const p = Promise.resolve()
      timerFunc = () => {
        p.then(flushCallbacks)
        if (isIOS) setTimeout(noop)
      }
      isUsingMicroTask = true
    // 优先级 2 用 MutationObserver 回调定义 microtask
    } else if (!isIE && typeof MutationObserver !== 'undefined' && (
      isNative(MutationObserver) ||
      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
      // 优先级 3 用 setImmediate 定义 macrotask
    } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
      timerFunc = () => {
        setImmediate(flushCallbacks)
      }
    } else {
      // 优先级 4 用 setTimeout定义 macrotask
      // Fallback to setTimeout.
      timerFunc = () => {
        setTimeout(flushCallbacks, 0)
      }
    }
    
    export 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
        })
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    nextTick 函数首先把传入的回调函数 cb 经过包装之后放入 callbacks 数组中,然后判断 pending 如果为假则调用 timerFunc 函数。timerFunc 函数是设置一个微任务(microtask),通过 Promise\MutationObserver 逐优先级实现,如果没有原生 Promise\MutationObserver,则用 setImmediate\setTimeout 设置一个宏任务(macrotask)。关于 microtask 和 macrotask 这里不作具体研究,小伙伴们可自行查阅相关资料学习。

    js 事件循环

    无论 timerFunc 设置的是微任务还是宏任务,他们的回调函数都是设置为 flushCallbacks 函数,在下一个任务调度的时候执行它。因为 nextTick 在一个同步周期内可能会被调用多次,而 timerFunc 只需设置一个调度任务即可,所以设置一个变量 pending 来标识 flushCallbacks 是否被触发执行,如果 pending 为 true,则已经设置好调度任务。

    // microtask/macrotask 回调函数
    function flushCallbacks () {
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在 flushCallbacks 函数中先把 pending 设为 false,即函数执行完毕后又可设置一个新的调度任务。然后遍历在 nextTick 函数中存入 callbacks 的回调函数逐个执行它们,这样我们传入 nextTick 的回调函数就被执行了。

    vue nextTick

    总结:

    nextTick 函数代码量不大,简而言之它就是设置一个任务(微任务优先,不支持则使用宏任务),在任务被调度执行时执行用户传入的回调函数。

  • 相关阅读:
    AUTOSAR汽车电子嵌入式编程精讲300篇-基于FPGA的LIN总线控制器设计与验证(续)
    LeetCode题:两数之和-2023/9/11
    Spring Start制作
    C++ 补充 反向迭代器的实现
    刷爆力扣之1 比特与 2 比特字符
    【三次握手、四次挥手】TCP建立连接和断开连接的过程、为什么需要三次握手,为什么需要四次挥手、TCP的可靠传输如何保证、为什么需要等待2MSL等重点知识汇总
    前端基础建设与架构21 如何利用 JavaScript 实现经典数据结构?
    java从键盘输入Scanner
    RPC概览
    汽车服务门店小程序模板制作指南
  • 原文地址:https://blog.csdn.net/dclnet/article/details/126083528