• 【揭秘Vue】nextTick的神秘面纱:原理与作用一览无余!


    前言 - JavaScript执行原理EventLoop

    JavaScript执行原理涉及到Event Loop(事件循环)的概念。JavaScript是一种单线程语言,意味着它一次只能执行一个任务。然而,JavaScript经常需要处理异步任务,比如网络请求、定时器等,而这些任务的执行不能阻塞主线程。这时候就需要Event Loop来协调异步任务的执行。

    Event Loop的基本工作原理:

    1. 执行栈(Call Stack): 执行栈是存储函数调用的栈结构。当一个函数被调用,它就会被推入执行栈,执行完毕后就会被弹出。这是一个同步执行的过程。
    2. 消息队列(Message Queue): 消息队列用来存放异步任务的回调函数。当异步任务完成后,会将其回调函数放入消息队列
    3. 事件循环(Event Loop): Event Loop是一个持续运行的循环,负责检查执行栈和消息队列。当执行栈为空时,Event Loop会检查消息队列是否有待执行的任务。如果有,就将任务的回调函数推入执行栈执行。这个过程一直循环进行,形成了事件循环。
    4. 宏任务(Macro Task)和微任务(Micro Task): 在事件循环中,任务分为宏任务和微任务。宏任务包括整体的script代码、setTimeout、setInterval等,而微任务包括Promise、process.nextTick等。微任务的优先级高于宏任务,会在当前宏任务执行完毕后立即执行。

    整个执行过程可以简要描述为:

    • 执行栈执行同步任务。
    • 当遇到异步任务时,将其回调函数加入消息队列。
    • 当执行栈为空时,Event Loop检查消息队列。
    • 如果消息队列中有任务,就将任务的回调函数推入执行栈执行。
    • 重复以上过程。

    下面是一个简单的例子,演示Event Loop的执行过程:

    console.log('Start');
    
    setTimeout(function() {
      console.log('Timeout callback');
    }, 0);
    
    Promise.resolve().then(function() {
      console.log('Promise callback');
    });
    
    console.log('End');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在上面的例子中,执行顺序是:

    1. 打印 ‘Start’
    2. 打印 ‘End’
    3. 打印 ‘Promise callback’
    4. 打印 ‘Timeout callback’

    这是因为Promise的回调函数属于微任务,它会在当前宏任务执行完毕后立即执行,而setTimeout的回调函数属于宏任务,会在下一个宏任务中执行。

    $nextTick的原理

    Vue的nextTick其本质是对JavaScript执行原理EventLoop的一种应用。

    nextTick的核心是利用了如PromiseMutationObserversetImmediatesetTimeout等原生JavaScript方法来模拟对应的微/宏任务的实现,本质是为了利用JavaScript的这些异步回调任务队列来实现Vue框架中自己的异步回调队列

    具体的解释

    nextTick 是 Vue.js 中一个重要的异步更新机制,它用于在 DOM 更新之后执行回调函数。Vue.js通过nextTick来确保在数据变化后,DOM 已经更新。

    JavaScript 的事件循环(Event Loop)分为宏任务队列(macrotask queue)和微任务队列(microtask queue)。nextTick的实现利用了这两个队列,以确保在 DOM 更新后执行回调。

    在 Vue.js 中,当数据发生变化时,Vue 异步执行 DOM 更新。在这个过程中,Vue 会将需要执行的回调函数推入一个队列中,而不是立即执行它们。这就是 nextTick 的核心机制。

    在现代浏览器中,Vue 使用 PromiseMutationObserver 来模拟微任务。如果两者都不可用,Vue 会回退到使用 setTimeout 来模拟宏任务。

    下面是一个简化的 nextTick 实现,用于说明其基本原理:

    let 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]()
      }
    }
    
    function nextTick(cb) { //用于将回调函数推入队列,并在适当的时机执行。
      callbacks.push(cb)
      if (!pending) {
        pending = true
        if (typeof Promise !== 'undefined') {
          Promise.resolve().then(flushCallbacks)
        } else if (typeof MutationObserver !== 'undefined') {
          const observer = new MutationObserver(flushCallbacks)
          const textNode = document.createTextNode('1')
          observer.observe(textNode, { characterData: true })
          textNode.data = '2'
        } else if (typeof setImmediate !== 'undefined') {
          setImmediate(flushCallbacks)
        } else {
          setTimeout(flushCallbacks, 0)
        }
      }
    }
    
    • 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

    nextTick 在不同环境下使用了不同的异步执行机制,确保了在现代浏览器和旧版本浏览器中都能够正常工作。这样,Vue 能够在数据变化后,等待 DOM 更新完成后执行用户传入的回调函数,从而实现更可靠的异步更新机制。

    $nextTick的作用

    nextTick不仅是Vue内部的异步队列的调用方法,同时也允许开发者在实际项目中使用这个方法来满足实际应用中对DOM更新数据时机的后续逻辑处理。

    nextTick是典型的将底层JavaScript执行原理应用到具体案例中的示例,引入异步更新队列机制的原因:

    • 如果是同步更新,则多次对一个或多个属性赋值,会频繁触发UI/DOM的渲染,可以减少一些无用渲染。
    • 同时由于VirtualDOM的引入,每一次状态发生变化后,状态变化的信号会发送给组件,组件内部使用VirtualDOM进行计算得出需要更新的具体的DOM节点,然后对DOM进行更新操作。每次更新状态后的渲染过程需要更多的计算,而这种无用功也将浪费更多的性能,所以异步渲染变得更加至关重要。

    Vue采用了数据驱动视图的思想,但是在一些情况下,仍然需要操作DOM。有时候,可能遇到这样的情况,DOM1的数据发生了变化,而DOM2需要从DOM1中获取数据,那这时就会发现DOM2的视图并没有更新,这时就需要用到了nextTick了。

    由于Vue的DOM操作是异步的,所以,在上面的情况中,就要将DOM2获取数据的操作写在$nextTick中。

    所以,在以下情况下,会用到nextTick

    • 在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的DOM结构的时候,这个操作就需要方法在nextTick()的回调函数中。
    
    
    
      
      
      Vue nextTick Example
    
    
    
    

    {{ message }}

    DOM Updated: {{ updatedMessage }}
    • 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

    在这个例子中,当点击按钮改变message数据时,我们使用this.$nextTick来确保在DOM更新之后获取更新后的文本内容,并在页面上显示。

    • 在Vue生命周期中,如果在created()钩子进行DOM操作,也一定要放在nextTick()的回调函数中。因为在created()钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM,所以,此时如果想要操作DOM,必须将操作的代码放在nextTick()的回调函数中。
    
    
    
      
      
      Vue nextTick Example
    
    
    
    
    • 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

    在这个例子中,我们在Vue实例的created钩子中创建一个

    元素,并使用this.$nextTick确保DOM已经渲染,然后将这个元素添加到页面中。

    源码附件:点此下载

  • 相关阅读:
    【高级篇 / ZTNA】(7.0) ❀ 01. FortiClient EMS 下载与安装 ❀ FortiGate 防火墙
    「指间灵动,快码加编」:阿里云通义灵码,再次降临博客园
    基于jeecgboot-vue3的Flowable流程-自定义业务表单处理(一)支持同一个业务多个关联流程的选择支持
    CUDA By Example(四)——线程协作
    2023-05-04:用go语言重写ffmpeg的scaling_video.c示例,用于实现视频缩放(Scaling)功能。
    web 服务搭建
    黑盒测试方法:原理+实战
    域名解析常见问题(中)
    替换SlowFast中Detectron2为Yolov8
    JavaScript从入门到精通 |循环
  • 原文地址:https://blog.csdn.net/CRMEB/article/details/134514852