Code前端首页关于Code前端联系我们

从Event Loop角度解读Vue NextTick源码

terry 2年前 (2023-09-08) 阅读数 161 #Vue

解读背景

  1. 在检查vue源代码时,nextTick方法使用浏览器的p事件作为loopp事件。同步更新。
  2. 在公司面试时,我最喜欢的笔试题是JavaScript工作机制、Promise/A+♽以及其他问题❙♽❙❙线程。
  3. 学习遵循Tick原则,帮助定位BUG并使用

    V。

什么是事件循环

我们先看图(来自大佬先生)

v2-d1ca0d6b13501044a5f74c99becbcd3d_b.gifv2-d1ca0d6b13501044a5f74c99becbcd3d_b.gif

  1. 首先完成同步阻塞任务。同步任务会等待上一个任务完成,然后再执行下一个任务。同步任务执行完毕后,再执行异步任务。当遇到异步任务时,将该异步任务的回调函数注册到异步任务队列中。 。注意,如果主线程中没有同步任务,则直接调用异步任务的微任务。
  2. 运行宏任务,所有检测到的微任务将被添加到微任务队列中。
  3. 开始完成微任务序列。一个宏任务完成后,会执行微任务队列,直到所有微任务队列完成且微任务队列为空。
  4. 完成宏任务。如果宏任务运行时存在微任务,则将微任务添加到微任务队列中。完成一个宏任务后,再完成一个微任务,直到微任务队列中的所有微任务都完成。
  5. 继续运行宏任务序列。

重复2、3、4、5...,直到宏任务和微任务都为空。

$nextTickin

实现原理

从字面上理解,下一个下一个ticktick(时钟)来自于定时器的周期性中断(输出脉冲和中断)。 ,又称为“时钟滴答声”),nextTick顾名思义,就是时钟的下一个滴答声。
看源码,在Vue 2.x版本中nextTicku是目录❙next-tick.js ,我们看到 nextTick 的重要性。虽然它只有200多行长,但创建一个单独的文件来维护它非常重要。

接下来,我们来看看整个文件。

  1. 声明了三个全局变量,callbacks: [],await:Boolean,timerFunc:undefined
  2. 声明函数 flushCallbacks
  3. 一组**if,else if**判断。
  4. 抛出函数 nextTick

nextTick函数

未命名文件 (1).png未命名文件 (1).png

  1. 声明局部变量 _resolve
  2. 将所有回调操作放入callback并将所有callback保存为堆栈。
  3. expectfalse时,执行定时器功能。
  4. 回调时,返回Promise回调,可以被♾t接收。

timerFunc函数

我们开始讨论将timerFunc 作为全局变量。现在调用timerFunc。 timerFunc什么时候被定义为一个函数,该函数的执行代码是什么?

image.pngimage.png
我们可以看到这段判断代码一共有四个分支,并且timerFunc在四个分支中有不同的赋值。我们先看第一个分支。

承诺分行

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // In problematic UIWebViews, Promise.then doesn't completely break, but
    // it can get stuck in a weird state where callbacks are pushed into the
    // microtask queue but the queue isn't being flushed, until the browser
    // needs to do some other work, e.g. handle a timer. Therefore we can
    // "force" the microtask queue to be flushed by adding an empty timer.
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
}
 
  1. 判断环境是否支持Promise,以及Promise是否是原创的。
  2. 使用 Promise

    异步调用 flushCallbacks

  3. 当执行环境为iPhone♂等时,使用setTimeout调用always,always iOS 在某些异常网页视图中 promise过期后没有任务队列更新,因此强制执行setTimeoutreshsh。

MutationObserver 分支

else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  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
}
 
  1. 评估是否可以使用非IE浏览器和HTML5新功能MutationObserver
  2. 实例化一个 MutationObserver 对象。此项主要是跟踪浏览器的变化观察设置DOM节点变化时会自动触发回调。
  3. timerFunc

    定义为当

    节点、♶
    function flushCallbacks () {
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
     
    D 冲洗时更改DOM节点的方法。 (其实这里需要使用MutationObserver属性来进行异步操作)

设置直属分支

else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Technically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
}
 
  1. 确定 setImmediate♂ 是否存在,setImmediate♂ 是较新版本 较新版本 仅支持 + edge。
  2. 如果启用,请跳过 flushCallbacks 运行 setImmediate♂ 。

setTimeout分支

else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
 
  1. 当以上所有异步分支api♂均不支持时,请使用宏♶♶♶
    function flushCallbacks () {
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
     
    setTimeout
    运行刷新回调。

运行较低版本

我们可以注意到,给timerFunc赋值是一个递减的过程。为什么呢,因为Vue在执行过程中执行环境是不一样的,所以它要适应环境。

未命名文件 (4).png未命名文件 (4).png

这张图可以帮助我们更清楚地理解底层流程。

冲洗功能

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
 

循环遍历,按照队列原则“先进先出”,逐一执行所有回调

总结

仅此而已。 nextTick♂的原理是利用Event♂

  • loop♂loop♂再次重新分支第一个事件线程❙。 Promise原因
  • 是同步JS代码执行时,执行栈首先检查micro♶task♶,如果不为空,(microtask不为空)microtask第一的。当DOM依赖于数据变化时,我们异步重新渲染DOM,但例如可以,❀ vas...这些Vue♂不行,要收集原始状态下的依赖DOM,我们需要手动执行
  • 重新渲染方法来重做❙。
  • 版权声明

    本文仅代表作者观点,不代表Code前端网立场。
    本文系作者Code前端网发表,如需转载,请注明页面地址。

    发表评论:

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

    热门