百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

图解 Vue 异步更新原理(vuex异步)

ztj100 2025-03-24 01:38 6 浏览 0 评论

本文主要分析 Vue 从 Data 更新,到通知 Watcher 异步更新视图的流程,也就是下图中的橙色部分。

我们先来回顾一下图中的几个对象:

  • Data 对象:Vue 中的 data 方法中返回的对象。
  • Dep 对象:每一个 Data 属性都会创建一个 Dep,用来搜集所有使用到这个 Data 的 Watcher 对象。
  • Watcher 对象:主要用于渲染 DOM。

接下来,我们就开始分析这个流程。

Vue 异步更新 DOM 原理

很多同学都知道,Vue 中的数据更新是异步的,意味着我们在修改完 Data 之后,并不能立刻获取修改后的 DOM 元素。

例如:



<script>
export default {
  data() {
    return {
      message: "hello",
    };
  },
  methods: {
    changeData() {
      this.message = "hello world";
      const textContent = document.getElementById("text").textContent;
      // 直接获取,不是最新的
      console.log(textContent === "hello world"); // false
            // $nextTick 回调中,是最新的
      this.$nextTick(() => {
        const textContent = document.getElementById("text").textContent;
        console.warn(textContent === "hello world"); // true
      });
    },
  },
};
</script>

什么时候我们才能获取到真正的 DOM 元素?

答:在 Vue 的 nextTick 回调中。

这一点在 Vue 官网有详细的介绍,但你是否有想过,为什么 Vue 需要通过 nextTick 方法才能获取最新的 DOM?

带着这个疑问,我们直接看一下源码。

// 当一个 Data 更新时,会依次执行以下代码
// 1. 触发 Data.set
// 2. 调用 dep.notify
// 3. Dep 会遍历所有相关的 Watcher 执行 update 方法
class Watcher {
  // 4. 执行更新操作
  update() {
    queueWatcher(this);
  }
}

const queue = [];

function queueWatcher(watcher: Watcher) {
  // 5. 将当前 Watcher 添加到异步队列
  queue.push(watcher);
  // 6. 执行异步队列,并传入回调
  nextTick(flushSchedulerQueue);
}

// 更新视图的具体方法
function flushSchedulerQueue() {
  let watcher, id;
  // 排序,先渲染父节点,再渲染子节点
  // 这样可以避免不必要的子节点渲染,如:父节点中 v-if 为 false 的子节点,就不用渲染了
  queue.sort((a, b) => a.id - b.id);
  // 遍历所有 Watcher 进行批量更新。
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    // 更新 DOM
    watcher.run();
  }
}

根据上面的代码,我们可以得出这样一个流程图:

图中可以看到,Vue 在调用 Watcher 更新视图时,并不会直接进行更新,而是把需要更新的 Watcher 加入到 Queue 队列里,然后把具体的更新方法 flushSchedulerQueue 传给 nextTick 进行调用。

接下来,我们分析一下 nextTick。

const callbacks = [];
let timerFunc;

function nextTick(cb?: Function, ctx?: Object) {
  let _resolve;
  // 1.将传入的 flushSchedulerQueue 方法添加到回调数组
  callbacks.push(() => {
    cb.call(ctx);
  });
  // 2.执行异步任务
  // 此方法会根据浏览器兼容性,选用不同的异步策略
  timerFunc();
}

可以看到,nextTick 函数非常简单,它只是将传入的 flushSchedulerQueue 添加到 callbacks 数组中,然后执行了 timerFunc 方法。

接下来,我们分析一下 timerFunc 方法。

let timerFunc;
// 判断是否兼容 Promise
if (typeof Promise !== "undefined") {
  timerFunc = () => {
    Promise.resolve().then(flushCallbacks);
  };
  // 判断是否兼容 MutationObserver
  // https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
} else if (typeof MutationObserver !== "undefined") {
  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);
  };
  // 判断是否兼容 setImmediate
  // 该方法存在一些 IE 浏览器中
} else if (typeof setImmediate !== "undefined") {
  // 这是一个宏任务,但相比 setTimeout 要更好
  timerFunc = () => {
    setImmediate(flushCallbacks);
  };
} else {
  // 如果以上方法都不知道,使用 setTimeout 0
  timerFunc = () => {
    setTimeout(flushCallbacks, 0);
  };
}

// 异步执行完后,执行所有的回调方法,也就是执行 flushSchedulerQueue
function flushCallbacks() {
  for (let i = 0; i < copies.length; i++) {
    callbacks[i]();
  }
}

可以看到,timerFunc 是根据浏览器兼容性创建的一个异步方法,它执行完成之后,会调用 flushSchedulerQueue 方法进行具体的 DOM 更新。

分析到这里,我们就可以得到一张整体的流程图了。

接下来,我们来完善一些判断逻辑。

  • 判断 has 标识,避免在一个 Queue 中添加相同的 Watcher。
  • 判断 waiting 标识,让所有的 Watcher 都在一个 tick 内进行更新。
  • 判断 flushing 标识,处理 Watcher 渲染时,可能产生的新 Watcher。如:触发了 v-if 的条件,新增的 Watcher 渲染。

结合以上判断,最终的流程图如下:

最后,我们分析一下,为什么 this.$nextTick 能够获取更新后的 DOM?

// 我们使用 this.$nextTick 其实就是调用 nextTick 方法
Vue.prototype.$nextTick = function (fn: Function) {
  return nextTick(fn, this);
};

可以看到,调用 this.$nextTick 其实就是调用了图中的 nextTick 方法,在异步队列中执行回调函数。根据先来后到原则,修改 Data 触发的更新异步队列会先得到执行,执行完成后就生成了新的 DOM ,接下来执行 this.$nextTick 的回调函数时,能获取到更新后的 DOM 元素了。

由于 nextTick 只是单纯通过 Promise 、SetTimeout 等方法模拟的异步任务,所以也可以手动执行一个异步任务,来实现和 this.$nextTick 相同的效果。

this.message = "hello world";
// 手动执行一个异步任务,也能获取最新的 DOM
Promise.resolve().then(() => {
  const textContent = document.getElementById("text").textContent;
  console.log(textContent === "hello world"); // true
});
setTimeout(() => {
  const textContent = document.getElementById("text").textContent;
  console.log(textContent === "hello world"); // true
});

思考与总结

本文从源码的角度,介绍了 Vue 异步更新的原理,来简单回顾一下吧。

  1. 修改 Vue 中的 Data 时,就会触发所有和这个 Data 相关的 Watcher 进行更新。
  2. 首先,会将所有的 Watcher 加入队列 Queue。
  3. 然后,调用 nextTick 方法,执行异步任务。
  4. 在异步任务的回调中,对 Queue 中的 Watcher 进行排序,然后执行对应的 DOM 更新。

文章来源:
https://segmentfault.com/a/1190000023649590

最后

刚整理 2020 年全套最新精品技术资料免费发给你! (原价最少8999元,超2000G!)

领取 看看下面!!

1、点赞 + 评论 (勾选 “转发” )

2、关注小编私信。点击头像,关注。并私信回复关键词: 1024

相关推荐

使用 Pinia ORM 管理 Vue 中的状态

转载说明:原创不易,未经授权,谢绝任何形式的转载状态管理是构建任何Web应用程序的重要组成部分。虽然Vue提供了管理简单状态的技术,但随着应用程序复杂性的增加,处理状态可能变得更具挑战性。这就是为什么...

Vue3开发企业级音乐Web App 明星讲师带你学习大厂高质量代码

Vue3开发企业级音乐WebApp明星讲师带你学习大厂高质量代码下栽课》jzit.top/392/...

一篇文章说清 webpack、vite、vue-cli、create-vue 的区别

webpack、vite、vue-cli、create-vue这些都是什么?看着有点晕,不要怕,我们一起来分辨一下。...

超赞 vue2/3 可视化打印设计VuePluginPrint

今天来给大家推荐一款非常不错的Vue可拖拽打印设计器Hiprint。引入使用//main.js中引入安装import{hiPrintPlugin}from'vue-plugin-...

搭建Trae+Vue3的AI开发环境(vue3 ts开发)

从2024年2025年,不断的有各种AI工具会在自媒体中火起来,号称各种效率王炸,而在AI是否会替代打工人的话题中,程序员又首当其冲。...

如何在现有的Vue项目中嵌入 Blazor项目?

...

Vue中mixin怎么理解?(vue的mixins有什么用)

作者:qdmryt转发链接:https://mp.weixin.qq.com/s/JHF3oIGSTnRegpvE6GSZhg前言...

Vue脚手架安装,初始化项目,打包并用Tomcat和Nginx部署

1.创建Vue脚手架#1.在本地文件目录创建my-first-vue文件夹,安装vue-cli脚手架:npminstall-gvue-cli安装过程如下图所示:创建my-first-vue...

新手如何搭建个人网站(小白如何搭建个人网站)

ElementUl是饿了么前端团队推出的桌面端UI框架,具有是简洁、直观、强悍和低学习成本等优势,非常适合初学者使用。因此,本次项目使用ElementUI框架来完成个人博客的主体开发,欢迎大家讨论...

零基础入门vue开发(vue快速入门与实战开发)

上面一节我们已经成功的安装了nodejs,并且配置了npm的全局环境变量,那么这一节我们就来正式的安装vue-cli,然后在webstorm开发者工具里运行我们的vue项目。这一节有两种创建vue项目...

.net core集成vue(.net core集成vue3)

react、angular、vue你更熟悉哪个?下边这个是vue的。要求需要你的计算机安装有o.netcore2.0以上版本onode、webpack、vue-cli、vue(npm...

使用 Vue 脚手架,为什么要学 webpack?(一)

先问大家一个很简单的问题:vueinitwebpackprjectName与vuecreateprojectName有什么区别呢?它们是Vue-cli2和Vue-cli3创建...

vue 构建和部署(vue项目部署服务器)

普通的搭建方式(安装指令)安装Node.js检查node是否已安装,终端输入node-v会使用命令行(安装)npminstallvue-cli-首先安装vue-clivueinitwe...

Vue.js 环境配置(vue的环境搭建)

说明:node.js和vue.js的关系:Node.js是一个基于ChromeV8引擎的JavaScript运行时环境;类比:Java的jvm(虚拟机)...

vue项目完整搭建步骤(vuecli项目搭建)

简介为了让一些不太清楚搭建前端项目的小白,更快上手。今天我将一步一步带领你们进行前端项目的搭建。前端开发中需要用到框架,那vue作为三大框架主流之一,在工作中很常用。所以就以vue为例。...

取消回复欢迎 发表评论: