认识Vue中的nextTick

一. 什么时候会用到nextTick

有时候有需求需要根据动态数据去为页面某些DOM元素添加事件,这就需要在DOM更新完毕之后去设置,但是createdmounted函数执行时一般dom没有渲染完毕,所以就会出现获取不到,添加不了事件问题,这就要用到nextTick处理了。

官网解释nextTick

 

 

二.Js运行机制

Js是单线程语言,它是基于事件循环,事件循环大致分为以下几个步骤

  1. 所有同步任务都在主线程上执⾏, 形成⼀个执⾏栈(execution context stack)。

  2. 线程之外, 还存在⼀个”任务队列“(task queue) 。 只要异步任务有了运⾏结果, 就在”任务队列”之中放置⼀个事件。

  3. 一旦”执⾏栈”中的所有同步任务执⾏完毕, 系统就会读取任务队列”, 看看⾥⾯有哪些事件。 那些对应异步任务, 于是结束等待状态, 进⼊执⾏栈, 开始执⾏。

  4. 不断重复第三步,直至”异步任务队列”全部清除

 

三.从源码理解nextTick

nextTick的源码位于 node_mondules/vue/src/core/util/nexttick.ts,总计118行,十分的短小精悍,去掉注释也就不到80行的代码,十分适合初次阅读源码同学

nextTick的源码要分为两部分:

  1. 环境检测(根据环境检测以不同的方式执行回调队列)众所周知,Event Loop分为宏任务(macro task)以及微任务( micro task),不管执行宏任务还是微任务,完成后都会进入一个tick,但是宏任务的执行时间要大于微任务,所以在浏览器支持的情况下优先使用微任务队列,浏览器支持微任务的情况下才去使用宏任务队列,但是在各种宏微任务之间,也有效率的不同,所以要根据浏览器支持情况,去创建不同的宏微任务,nextTick在环境检测这块,就是遵循的这种行为

    let timerFunc
    // 判断是否支持 Promise 浏览器是否支持原生setImmediate(setImmediate只在IE中有效)
    if (typeof Promise !== 'undefined' && isNative(Promise)) {
      const p = Promise.resolve()
      timerFunc = () => {
        p.then(flushCallbacks)
        if (isIOS) setTimeout(noop)
      }
      isUsingMicroTask = true
     //判断是否支持 MutationObserver
    } 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
      // 
    } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
      timerFunc = () => {
        setImmediate(flushCallbacks)
      }
       // 都不支持的情况下,使用setTimeout
    } else {
      timerFunc = () => {
        setTimeout(flushCallbacks, 0)
      }
    }
  2. 执行函数回调队列

    // 回调函数队列
    const callbacks = []
    let pending = falsefunction flushCallbacks() {
      pending = false
      // 防止出现nextTick中包含nextTick时出现问题,在执行回调函数队列前,提前复制备份清空回调函数队列
      const copies = callbacks.slice(0)
      callbacks.length = 0
      // 执行函数队列
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
    ​
    // 我们调用的nextTick函数
    export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
      let _resolve
    // 将我们调用nextTick函数传入的回调函数推入异步回调队列中
      callbacks.push(() => {
        if (cb) {
          try {
            cb.call(ctx)
          } catch (e: any) {
            handleError(e, ctx, 'nextTick')
          }
        } else if (_resolve) {
          _resolve(ctx)
        }
      })
      if (!pending) {
        pending = true
        timerFunc()
      }
    // 如果没有传入回调,并且支持Promise 返回一个新的Promise
      if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
      }
    }

四.总结

总体流程就是nextTick接收一个回调函数,将回调函数推入回调队列中,同时,在接收第一个回调函数时,环境检测中对应的异步方法(异步方法调用了回调函数队列)。我们自己简单实现一个nextTick:

    let callbacks = []
    let pending = false
    function nextTick(callback) {
        if (callback) {
            callbacks.push(callback)
        }
        if (!pending) {
            pending = true
            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]()
         }
     } 

可以看到我们实现简易版的nextTick中,通过nextTick接收回调函数,通过setTimeout来异步执行回调函数。通过这种方式可以实现在下一个tick中执行回调函数,即在DOM更新执行回调函数。

原文地址:https://blog.csdn.net/weixin_55034688/article/details/127126058

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_16331.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注