当前位置:首页 > 技术分析 > 正文内容

图解 Vue 异步更新原理_vue异步处理

ruisui884个月前 (02-09)技术分析20

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

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

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

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

Vue 异步更新 DOM 原理

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

例如:



什么时候我们才能获取到真正的 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

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/1818.html

标签: vue $nexttick
分享给朋友:

“图解 Vue 异步更新原理_vue异步处理” 的相关文章

Windows 下 Git 拉 Gitlab 代码

读者提问:『阿常你好,Windows 下 Git 拉 Gitlab 代码的操作步骤可以分享一下吗?』阿常回答:好的,总共分为五个步骤。一、Windows 下安装 Git官网下载链接:https://git-scm.com/download/winStandalone Installer(安装版)注意...

编码 10000 个小时后,开发者悟了:“不要急于发布!”

【CSDN 编者按】在软件开发的道路上,时间是最好的老师。根据“一万小时定律”,要成为某个领域的专家,通常需要大约一万小时的刻意练习。本文作者身为一名程序员,也经历了一万小时的编程,最终悟出了一个道理:慢即是快,重视架构设计和代码质量,确保每一行代码都经得起时间的考验。作者 | Sotiris Ko...

深度解析!AI智能体在To B领域应用,汽车售后服务落地全攻略

在汽车售后服务领域,AI智能体的应用正带来一场效率和专业度的革命。本文深度解析了一个AI智能体在To B领域的实际应用案例,介绍了AI智能体如何通过提升服务顾问和维修技师的专业度及维修效率,优化汽车售后服务流程。上周我分享了AI智能体+AI小程序To C的AI应用场景《1000%增长!我仅用一个小时...

Python中的11 种数组算法

1. 创建数组 创建数组意味着留出一个连续的内存块来存储相同类型的元素。在大多数语言中,您可以在创建数组时指定数组的大小。假设您正在书架上整理一组书籍,并且您需要为正好 10 本书预留空间。功能架上的每个空间都对应于数组中的一个索引。# Example in Python arr = [1, 2,...

一文看懂企业微信开发简易教程

为让开发者快速理解开发流程,本篇章展示如何一步步设计一个能与企业后台互动的自建应用。添加自建应用登录企业微信管理端 -> 应用与小程序 -> 应用 -> 自建,点击“创建应用”,设置应用logo、应用名称等信息,创建应用。创建完成后,在管理端的应用列表里进入该应用,可以看到agen...

微信开发的五大价值应用

企业形象展示微网站是企业在移动互联网时代完美展示企业及品牌形象的最佳选择,表现内容丰富、实时更新、形式多样化,保证品牌形象的有效传播!微网站带来的轻营销模式,更适应现代网站的发展模式,所以微网站的开发也具有更好的商业营销效果,其面对的受众是7亿多的微信用户,蕴含着无限的商机。将企业微网站植入微信公众...