Vue nextTick延迟回调获取更新后DOM机制详解

目录
  • 简述
  • 事件循环机制
  • vue数据驱动视图的处理(异步变化DOM)
  • Vue.nextTick原理
  • Vue.nextTick的应用
    • created生命周期中操作DOM
    • 修改数据,获取DOM值

简述

相信大家在写vue项目的时候,一定会发现一个神奇的api,Vue.nextTick。为什么说它神奇呢,那是因为在你做某些操作不生效时,将操作写在Vue.nextTick内,就神奇的生效了。那这是什么原因呢?

让我们一起来研究一下。

  • vue 实现响应式并不是数据发生变化后 DOM 立即变化,而是按照一定策略异步执行 DOM 更新的
  • vue 在修改数据后,视图不会立刻进行更新,而是要等同一事件循环机制内所有数据变化完成后,再统一进行DOM更新
  • nextTick 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。

事件循环机制

在讨论Vue.nextTick之前,需要先搞清楚事件循环机制,算是实现的基石了,那我们来看一下。

在浏览器环境中,我们可以将我们的执行任务分为宏任务和微任务,

  • 宏任务: 包括整体代码script,setTimeoutsetIntervalsetImmediate、 I/O 操作、UI 渲染
  • 微任务: Promise.thenMuationObserver

事件循环的顺序,决定js代码的执行顺序。事件循环如下:

用代码解释,浏览器中事件循环的顺序同如下代码:

for (macroTask of macroTaskQueue) {
    // 1. 执行一个宏任务
    handleMacroTask();
    // 2. 执行所有的微任务
    for (microTask of microTaskQueue) {
        handleMicroTask(microTask);
    }
 }

vue数据驱动视图的处理(异步变化DOM)

<template>
  <div>
    <div>{{count}}</div>
    <div @click="handleClick">click</div>
  </div>
</template>
export default {
    data () {
        return {
            number: 0
        };
    },
    methods: {
        handleClick () {
            for(let i = 0; i < 10000; i++) {
                this.count++;
            }
        }
    }
}

分析上述代码:

  • 当点击按钮时,count会被循环改变10000次。那么每次count+1,都会触发count的setter方法,然后修改真实DOM。按此逻辑,这整个过程,DOM会被更新10000次,我们都知道DOM的操作是非常昂贵的,而且这样的操作完全没有必要。所以vue内部在派发更新时做了优化
  • 也就是,并不会每次数据改变都触发 watcher 的回调,而是把这些 watcher 先添加到一个队列queueWatcher里,然后在 nextTick 后执行 flushSchedulerQueue处理
  • 当 count 增加 10000 次时,vue内部会先将对应的 Watcher 对象给 push 进一个队列 queue 中去,等下一个 tick 的时候再去执行。并不需要在下一个 tick 的时候执行 10000 个同样的 Watcher 对象去修改界面,而是只需要执行一个 Watcher 对象,使其将界面上的 0 变成 10000 即可

Vue.nextTick原理

由上一节我们知道,Vue中 数据变化 => DOM变化 是异步过程,一旦观察到数据变化,Vue就会开启一个任务队列,然后把在同一个事件循环 (Event loop) 中观察到数据变化的 Watcher(Vue源码中的Wacher类是用来更新Dep类收集到的依赖的)推送进这个队列。

如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOM操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。

nextTick的作用是为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback),JS是单线程的,拥有事件循环机制,nextTick的实现就是利用了事件循环的宏任务和微任务。

vue中next-tick.js的源码如下

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
export let isUsingMicroTask = false
// 首先定义一个 callbacks 数组用来存储 nextTick,在下一个 tick 处理这些回调函数之前,
// 所有的 cb 都会被存在这个 callbacks 数组中
const callbacks = []
// pending 是一个标记位,代表一个等待的状态
let pending = false
// 最后执行 flushCallbacks() 方法,遍历callbacks数组,依次执行里边的每个函数
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.then(微任务)
2.尝试使用MuationObserver(微任务)回调
3.尝试使用 setImmediate(宏任务)回调
4.最后尝试使用setTimeout(宏任务)回调
*/
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  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)
  }
} else {
  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
    })
  }
}

目前浏览器平台并没有实现 nextTick 方法,所以 Vue.js 源码中分别用 Promise、setTimeout、setImmediate 等方式在 microtask(或是task)中创建一个事件,目的是在当前调用栈执行完毕以后(不一定立即)才会去执行这个事件。

nextTick的调用方式

回调函数方式:Vue.nextTick(callback)

Promise方式:Vue.nextTick().then(callback)

实例方式:vm.$nextTick(callback)

Vue.nextTick的应用

created生命周期中操作DOM

created钩子函数执行的时候DOM 其实并未进行挂载和渲染,此时就是无法操作DOM的,我们将操作DOM的代码中放到nextTick中,等待下一轮事件循环开始,DOM就已经进行挂载好了,而与这个操作对应的就是mounted钩子函数,因为在mounted执行的时候所有的DOM挂载已完成。

created(){
  vm.$nextTick(() => {
      //不使用this.$nextTick()方法操作DOM会报错
      this.$refs.test.innerHTML="created中操作了DOM"
  });
}

修改数据,获取DOM值

当我们修改了data里的数据时,并不能立刻通过操作DOM去获取到里面的值

<template>
  <div class="test">
    <p ref='msg' id="msg">{{msg}}</p>
  </div>
</template>
<script>
export default {
  name: 'Test',
  data () {
    return {
      msg:"hello world",
    }
  },
  methods: {
    changeMsg() {
      this.msg = "hello Vue"  // vue数据改变,改变了DOM里的innerText
      let msgEle = this.$refs.msg.innerText  //后续js对dom的操作
      console.log(msgEle)  // hello world
      // 输出可以看到data里的数据修改后DOM并没有立即更新,后续的DOM不是最新的
      this.$nextTick(() => {
        console.log(this.$refs.msg.innerText) // hello Vue
      })
      this.$nextTick().then(() => {
        console.log(this.$refs.msg.innerText) // hello Vue
      })
    },
    changeMsg2() {
      this.$nextTick(() => {
        console.log(this.$refs.msg.innerText) // 1.hello world
      })
      this.msg = "hello Vue" // 2.
      console.log(this.$refs.msg.innerText) // hello world
      this.$nextTick().then(() => {
        console.log(this.$refs.msg.innerText) // hello Vue
      })
      // nextTick中先添加的先执行,执行1后,才会执行2(Vue操作Dom的异步)
    }
  }
}
</script>

v-show/v-if由隐藏变为显示

点击按钮显示原本以 v-show=false或v-if 隐藏起来的输入框,并获取焦点或者获得宽高等的场景

以上就是Vue nextTick延迟回调获取更新后DOM机制详解的详细内容,更多关于Vue nextTick延迟回调DOM获取的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue中的nextTick作用和几个简单的使用场景

    目的 理解下 nextTick 的作用和几个简单的使用场景 正文 起什么作用? 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 我想各位都知道或了解 Vue 的渲染流程,Vue 在监听到数据变化后会重新渲染,配合 VDOM 更新真实的 DOM,而 nextTick 的触发时机就是在调用方法后的第一次重新渲染完毕后. 如何使用? 有两种使用方法,一种是传入回调,另一种是 Promise,官方使用示例如下: // 修改数据 vm.msg = 'H

  • Vue异步更新机制及$nextTick原理的深入讲解

    目录 前言 Vue的异步更新 DOM更新是异步的 DOM更新还是批量的 事件循环 执行过程 源码深入 异步更新队列 nextTick $nextTick 总结 一般更新DOM是同步的 既然更新DOM是个同步的过程,那为什么Vue却需要借用$nextTick来处理呢? 为什么优先使用微任务? 总结 前言 相信很多人会好奇Vue内部的更新机制,或者平时工作中遇到的一些奇怪的问题需要使用$nextTick来解决,今天我们就来聊一聊Vue中的异步更新机制以及$nextTick原理 Vue的异步更新 可能

  • Vue NextTick介绍与使用原理

    目录 一.NextTick是什么 定义 理解 为什么要有nexttick 二.使用场景 三.实现原理 一.NextTick是什么 定义 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 理解 Vue在更新DOM时时异步执行的.当数据发生变化时,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新. 举例: <body> <div id="app"> <div> <

  • 谈谈Vue中的nextTick

    当数据发生变化之后,DOM视图并不会立即更新,如果我们在发生变化之后立马去获取某个节点或者某个节点的值,很有可能结果就是undefined:因为Vue实现响应式并不是数据发生变化之后DOM立即变化,而是按一定的策略进行DOM的更新: 来看一个小demo: App.vue <template> <div id="app"> <div ref="message">{{msg}}</div> <div v-if=&qu

  • Vue.nextTick纯干货使用方法详解

    目录 一.nextTick小测试 二.nextTick源码实现 1. 全局变量 2. flushCallbacks 3. nextTick的异步实现 4. nextTick方法实现 三.Vue组件的异步更新 1. queueWatcher做了什么? 2. flushSchedulerQueue做了什么? 四.回归题目本身 用过Vue的朋友多多少少都知道$nextTick- 在正式讲解nextTick之前,我想你应该清楚知道 Vue 在更新 DOM 时是异步执行的,因为接下来讲解过程会结合组件更新

  • 一文学会什么是vue.nextTick()

    目录 概念 原理 举例 应该什么时候使用vue.$nextTick()呢? 概念 在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的DOM 简单理解: 为了去解决数据更新了但是视图不更新的问题,当数据更新了,在DOM渲染后,自动执行该函数 原理 Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列.如果这个watcher被触发多次,只会被

  • Vue nextTick获取更新后的DOM的实现

    目录 生命周期 update Vue.nextTick Promise 结语&参考资料 前两天在开发时遇到一个需求:打开对话框的时候自动聚焦其中的输入框.由于原生的 autofocus 属性不起作用,需要使用组件库提供的 focus 方法手动手动获取焦点.于是有如下代码: <el-button @click="openDialog">点击打开 Dialog</el-button> <el-dialog :visible.sync="dia

  • Vue nextTick的原理解析

    使用过Vue的小伙伴们都知道,Vue里的nextTick可以获取到更新后的DOM, 今天我就来讲解下nextTick里面究竟做了什么? 开始讲解前,我们需要知道了解一个概念,那就是Event Loop Event Loop Event Loop翻译过来就是事件循环, 一个Event Loop会包括一个或多个task队列,持续线程会从队列中取出最早进入队列的任务进行执行,被取出的任务就叫做macroTask(宏任务), 每个macroTask都有一个任务源, 每个macroTask处理完之后就从队

  • 浅析vue中的nextTick

    背景 vue是异步渲染的,当data改变之后,DOM不会立刻被渲染,页面渲染时会将data的修改做整合,多次data修改只会做整合最后一次性渲染出来,这也是异步渲染的原因.只有异步渲染才可以实现整合操作. 例子 methods: { update() { for (let i = 0; i < 10; i++) { this.testNum = this.testNum + i; } }, }, 在你的 Vue 视图中, testNum 会发生变化.不过需要注意的是这个变化的过程,虽然我们把 f

  • Vue nextTick延迟回调获取更新后DOM机制详解

    目录 简述 事件循环机制 vue数据驱动视图的处理(异步变化DOM) Vue.nextTick原理 Vue.nextTick的应用 created生命周期中操作DOM 修改数据,获取DOM值 简述 相信大家在写vue项目的时候,一定会发现一个神奇的api,Vue.nextTick.为什么说它神奇呢,那是因为在你做某些操作不生效时,将操作写在Vue.nextTick内,就神奇的生效了.那这是什么原因呢? 让我们一起来研究一下. vue 实现响应式并不是数据发生变化后 DOM 立即变化,而是按照一定

  • Vue $nextTick 为什么能获取到最新Dom源码解析

    目录 正文 修改数据之后 update 方法 nextTick 方法里面怎么执行传进去更新方法 正文 <template> <p id='text'>{{text}}</p> <button @click='change'>click</button> </template> <script> export default { data() { return { text: 'hello world' } } method

  • vue的路由守卫和keep-alive后生命周期详解

    目录 Vue-Router懒加载 如何定义动态路由 param方式 query方式 Vue-Router导航守卫 全局路由钩子 router.beforeEach 路由独享守卫 组件内的守卫 beforeRouterEnter beforeRouteUpdate beforeRouteLeave Vue路由钩子在生命周期函数的体现 keep-alive 生命周期钩子 activated deactivated 触发钩子的完整顺序 总结 Vue-Router懒加载 1.箭头函数+import co

  • Vue 2.0 侦听器 watch属性代码详解

    用法 -------------------------------------------------------------------------------- 先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的表达式(函数),还可以是一个对象,可以包含如下几个属性: handler        ;对应的函数                          ;可以带两个参数,分别是新的值

  • Vue 2阅读理解之initRender与callHook组件详解

    目录 initRender 组件渲染初始化 callHook('beforeCreate') initRender 组件渲染初始化 在 initEvents 事件系统初始化完成之后,紧接着的就是组件实例的渲染部分的初始化 initRender. initRender 函数定义位于 src/core/instance/render.ts 文件内,基本定义如下: export function initRender(vm: Component) { vm._vnode = null vm._stat

  • jQuery原理系列-常用Dom操作详解

    1. 事件绑定$(el).bind ie使用attachEvent,其它浏览器使用addEventListener,不同的是ie多了个on前缀,this绑定在window上,需要用call和apply修正this 的指向. if (element.addEventListener) { element.addEventListener(type, handler, useCapture); } else { if (element.attachEvent) { element.attachEve

  • vue中各选项及钩子函数执行顺序详解

    在vue中,实例选项和钩子函数和{{}}表达式都是不需要手动调用就可以直接执行的. vue的生命周期如下图: 在页面首次加载执行顺序有如下: beforeCreate //在实例初始化之后.创建之前执行 created //实例创建后执行 beforeMounted //在挂载开始之前调用 filters //挂载前加载过滤器 computed //计算属性 directives-bind //只调用一次,在指令第一次绑定到元素时调用 directives-inserted //被绑定元素插入父

  • React为 Vue 引入容器组件和展示组件的教程详解

    如果你使用过 Redux 开发 React,你一定听过 容器组件(Smart/Container Components) 或 展示组件(Dumb/Presentational Components),这样划分有什么样的好处,我们能否能借鉴这种划分方式来编写 Vue 代码呢?这篇文章会演示为什么我们应该采取这种模式,以及如何在 Vue 中编写这两种组件. 为什么要使用容器组件? 假如我们要写一个组件来展示评论,在没听过容器组件之前,我们的代码一般都是这样写的: components/Comment

  • vue中keep-alive、activated的探讨和使用详解

    在修改公司的一个项目的时候发现了activated这个东西,一直觉得很疑惑,之前也没怎么用过啊!官网的生命周期那也没说过这东西啊!生命周期不就create mount update 和destory这几个东东么,怎么多了个activate出来. 百思不得其解,于是去问了下度娘和查了下文档!恍然大悟,原来这东东是结合keep-alive这东东使用的,下面顺便记录一下. keep-alive <keep-alive>包裹动态组件的时候,会缓存不活动的组件实例,而不是摧毁他们.其是一个抽象的组件,自

随机推荐