验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

Vue2和Vue3的nextTick怎么实现

阅读:541 来源:乙速云 作者:代码code

Vue2和Vue3的nextTick怎么实现

      一次弄懂 Vue2 和 Vue3 的 nextTick 实现原理

      Vue2 中的 nextTick

      在 Vue2 中,nextTick 的实现基于浏览器的异步任务队列和微任务队列。

      异步任务队列

      在浏览器中,每个宏任务结束后会检查微任务队列,如果有任务则依次执行。当所有微任务执行完成后,才会执行下一个宏任务。因此可以通过将任务作为微任务添加到微任务队列中,来确保任务在所有宏任务执行完毕后立即执行。

      而使用 setTimeout 可以将任务添加到异步任务队列中,在下一轮事件循环中执行。

      在 Vue2 中,如果没有指定执行环境,则会优先使用 Promise.then / MutationObserver,否则使用 setTimeout。

      javascript
      // src/core/util/next-tick.js
      
      /* istanbul ignore next */
      const callbacks = []
      let pending = false
      
      function flushCallbacks() {
        pending = false
        const copies = callbacks.slice(0)
        callbacks.length = 0
        for (let i = 0; i < copies.length; i++) {
          copies[i]()
        }
      }
      
      let microTimerFunc
      let macroTimerFunc
      let useMacroTask = false
      
      if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
        // 使用 setImmediate
        macroTimerFunc = () => {
          setImmediate(flushCallbacks)
        }
      } else if (
        typeof MessageChannel !== 'undefined' &&
        (isNative(MessageChannel) ||
          // PhantomJS
          MessageChannel.toString() === '[object MessageChannelConstructor]')
      ) {
        const channel = new MessageChannel()
        const port = channel.port2
        channel.port1.onmessage = flushCallbacks
        macroTimerFunc = () => {
          port.postMessage(1)
        }
      } else {
        // 使用 setTimeout
        macroTimerFunc = () => {
          setTimeout(flushCallbacks, 0)
        }
      }
      
      if (typeof Promise !== 'undefined' && isNative(Promise)) {
        // 使用 Promise.then
        const p = Promise.resolve()
        microTimerFunc = () => {
          p.then(flushCallbacks)
        }
      } else {
        // 使用 MutationObserver
        const observer = new MutationObserver(flushCallbacks)
        const textNode = document.createTextNode(String(1))
        observer.observe(textNode, {
          characterData: true
        })
        microTimerFunc = () => {
          textNode.data = String(1)
        }
      }
      
      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
          if (useMacroTask) {
            macroTimerFunc()
          } else {
            microTimerFunc()
          }
        }
        if (!cb && typeof Promise !== 'undefined') {
          return new Promise(resolve => {
            _resolve = resolve
          })
        }
      }

      宏任务和微任务

      在 Vue2 中,可以通过设置 useMacroTask 来使 nextTick 方法使用宏任务或者微任务。

      Vue2 中默认使用微任务,在没有原生 Promise 和 MutationObserver 的情况下,才会改用 setTimeout。

      javascript
      let microTimerFunc
      let macroTimerFunc
      let useMacroTask = false // 默认使用微任务
      
      if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
        // 使用 setImmediate
        macroTimerFunc = () => {
          setImmediate(flushCallbacks)
        }
      } else if (
        typeof MessageChannel !== 'undefined' &&
        (isNative(MessageChannel) ||
          // PhantomJS
          MessageChannel.toString() === '[object MessageChannelConstructor]')
      ) {
        const channel = new MessageChannel()
        const port = channel.port2
        channel.port1.onmessage = flushCallbacks
        macroTimerFunc = () => {
          port.postMessage(1)
        }
      } else {
        // 使用 setTimeout
        macroTimerFunc = () => {
          setTimeout(flushCallbacks, 0)
        }
      }
      
      if (typeof Promise !== 'undefined' && isNative(Promise)) {
        // 使用 Promise.then
        const p = Promise.resolve()
        microTimerFunc = () => {
          p.then(flushCallbacks)
        }
      } else {
        // 使用 MutationObserver
        const observer = new MutationObserver(flushCallbacks)
        const textNode = document.createTextNode(String(1))
        observer.observe(textNode, {
          characterData: true
        })
        microTimerFunc = () => {
          textNode.data = String(1)
        }
      }
      
      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
          if (useMacroTask) {
            macroTimerFunc()
          } else {
            microTimerFunc()
          }
        }
        if (!cb && typeof Promise !== 'undefined') {
          return new Promise(resolve => {
            _resolve = resolve
          })
        }
      }

      总结

      在 Vue2 中,nextTick 的实现原理基于浏览器的异步任务队列和微任务队列。Vue2 默认使用微任务,在没有原生 Promise 和 MutationObserver 的情况下才会改用 setTimeout。

      Vue3 中的 nextTick

      在 Vue3 中,nextTick 的实现有了较大变化,主要是为了解决浏览器对 Promise 的缺陷和问题。

      Promise 在浏览器中的问题

      在浏览器中,Promise 有一个缺陷:如果 Promise 在当前事件循环中被解决,那么在 then 回调函数之前添加的任务将不能在同一个任务中执行。

      例如:

      javascript
      Promise.resolve().then(() => {
        console.log('Promise 1')
      }).then(() => {
        console.log('Promise 2')
      })
      
      console.log('Hello')

      输出结果为:

      Hello
      Promise 1
      Promise 2

      这是因为 Promise 虽然是微任务,但是需要等到当前宏任务结束才能执行。

      Vue3 中解决 Promise 缺陷的方法

      在 Vue3 中,通过使用 MutationObserver 和 Promise.resolve().then() 来解决 Promise 在浏览器中的缺陷。具体实现如下:

      javascript
      const queue: Array = []
      let has: { [key: number]: boolean } = {}
      let flushing = false
      let index = 0
      
      function resetSchedulerState() {
        queue.length = 0
        has = {}
        flushing = false
      }
      
      function flushSchedulerQueue() {
        flushing = true
        let job
        while ((job = queue.shift())) {
          if (!has[job.id]) {
            has[job.id] = true
            job()
          }
        }
        resetSchedulerState()
      }
      
      let macroTimerFunc
      let microTimerFunc
      
      if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
        macroTimerFunc = () => {
          setImmediate(flushSchedulerQueue)
        }
      } else {
        macroTimerFunc = () => {
          setTimeout(flushSchedulerQueue, 0)
        }
      }
      
      if (typeof Promise !== 'undefined' && isNative(Promise)) {
        const p = Promise.resolve()
        microTimerFunc = () => {
          p.then(flushSchedulerQueue)
          if (isIOS) setTimeout(noop)
        }
      } else {
        microTimerFunc = macroTimerFunc
      }
      
      export function nextTick(fn?: Function): Promise {
        const id = index++
        const job = fn.bind(null)
        queue.push(job)
      
        if (!flushing) {
          if (useMacroTask) {
            macroTimerFunc()
          } else {
            microTimerFunc()
          }
        }
      
        if (!fn && typeof Promise !== 'undefined') {
          return new Promise(resolve => {
            resolvedPromise.then(() => {
              if (has[id] || !queue.includes(job)) {
                return
              }
              queue.splice(queue.indexOf(job), 1)
              resolve()
            })
          })
        }
      }

      在 Vue3 中,nextTick 的实现原理基于MutationObserver 和 Promise.resolve().then(),通过 MutationObserver 监测 DOM 变化,在下一个微任务中执行回调函数。

      而如果当前浏览器不支持原生 Promise,则使用 setTimeout 来模拟 Promise 的行为,并在回调函数执行前添加一个空的定时器来强制推迟执行(解决 iOS 中 setTimeout 在非激活标签页中的问题)。

      如果需要等待所有回调函数执行完成,则可以通过返回一个 Promise 对象来实现。

      javascript
      export function nextTick(fn?: Function): Promise {
        const id = index++
        const job = fn.bind(null)
        queue.push(job)
      
        if (!flushing) {
          if (useMacroTask) {
            macroTimerFunc()
          } else {
            microTimerFunc()
          }
        }
      
        if (!fn && typeof Promise !== 'undefined') {
          return new Promise(resolve => {
            resolvedPromise.then(() => {
              if (has[id] || !queue.includes(job)) {
                return
              }
              queue.splice(queue.indexOf(job), 1)
              resolve()
            })
          })
        }
      }
    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>