批量异步更新策略及 nextTick 原理

为什么要异步更新

通过前面几个章节我们介绍,相信大家已经明白了 Vue.js 是如何在我们修改 data 中的数据后修改视图了。简单回顾一下,这里面其实就是一个“setter -> Dep -> Watcher -> patch -> 视图”的过程。

假设我们有如下这么一种情况。

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

当我们按下 click 按钮的时候,number 会被循环增加1000次。

那么按照之前的理解,每次 number 被 +1 的时候,都会触发 number 的 setter 方法,从而根据上面的流程一直跑下来最后修改真实 DOM。那么在这个过程中,DOM 会被更新 1000 次!太可怕了。

Vue.js 肯定不会以如此低效的方法来处理。Vue.js在默认情况下,每次触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,在下一个 tick 的时候将这个队列 queue 全部拿出来 run( Watcher 对象的一个方法,用来触发 patch 操作) 一遍。

那么什么是下一个 tick 呢?

nextTick

Vue.js 实现了一个 nextTick 函数,传入一个 cb ,这个 cb 会被存储到一个队列中,在下一个 tick 时触发队列中的所有 cb 事件。

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

笔者用 setTimeout 来模拟这个方法,当然,真实的源码中会更加复杂,笔者在小册中只讲原理,有兴趣了解源码中 nextTick 的具体实现的同学可以参考next-tick。

首先定义一个 callbacks 数组用来存储 nextTick,在下一个 tick 处理这些回调函数之前,所有的 cb 都会被存在这个 callbacks 数组中。pending 是一个标记位,代表一个等待的状态。

setTimeout 会在 task 中创建一个事件 flushCallbacks ,flushCallbacks 则会在执行时将 callbacks 中的所有 cb 依次执行。

let callbacks = [];
let pending = false;function nextTick (cb) {callbacks.push(cb);if (!pending) {pending = true;setTimeout(flushCallbacks, 0);}
}function flushCallbacks () {pending = false;const copies = callbacks.slice(0);callbacks.length = 0;for (let i = 0; i < copies.length; i++) {copies[i]();}
}

再写 Watcher

第一个例子中,当我们将 number 增加 1000 次时,先将对应的 Watcher 对象给 push 进一个队列 queue 中去,等下一个 tick 的时候再去执行,这样做是对的。但是有没有发现,另一个问题出现了?

因为 number 执行 ++ 操作以后对应的 Watcher 对象都是同一个,我们并不需要在下一个 tick 的时候执行 1000 个同样的 Watcher 对象去修改界面,而是只需要执行一个 Watcher 对象,使其将界面上的 0 变成 1000 即可。

那么,我们就需要执行一个过滤的操作,同一个的 Watcher 在同一个 tick 的时候应该只被执行一次,也就是说队列 queue 中不应该出现重复的 Watcher 对象。

那么我们给 Watcher 对象起个名字吧~用 id 来标记每一个 Watcher 对象,让他们看起来“不太一样”。

实现 update 方法,在修改数据后由 Dep 来调用, 而 run 方法才是真正的触发 patch 更新视图的方法。

let uid = 0;class Watcher {constructor () {this.id = ++uid;}update () {console.log('watch' + this.id + ' update');queueWatcher(this);}run () {console.log('watch' + this.id + '视图更新啦~');}
}

queueWatcher

不知道大家注意到了没有?笔者已经将 Watcher 的 update 中的实现改成了

queueWatcher(this);

将 Watcher 对象自身传递给 queueWatcher 方法。

我们来实现一下 queueWatcher 方法。

let has = {};
let queue = [];
let waiting = false;function queueWatcher(watcher) {const id = watcher.id;if (has[id] == null) {has[id] = true;queue.push(watcher);if (!waiting) {waiting = true;nextTick(flushSchedulerQueue);}}
}

我们使用一个叫做 has 的 map,里面存放 id -> true ( false ) 的形式,用来判断是否已经存在相同的 Watcher 对象 (这样比每次都去遍历 queue 效率上会高很多)。

如果目前队列 queue 中还没有这个 Watcher 对象,则该对象会被 push 进队列 queue 中去。

waiting 是一个标记位,标记是否已经向 nextTick 传递了 flushSchedulerQueue 方法,在下一个 tick 的时候执行 flushSchedulerQueue 方法来 flush 队列 queue,执行它里面的所有 Watcher 对象的 run 方法。

flushSchedulerQueue

function flushSchedulerQueue () {let watcher, id;for (index = 0; index < queue.length; index++) {watcher = queue[index];id = watcher.id;has[id] = null;watcher.run();}waiting  = false;
}

举个例子

let watch1 = new Watcher();
let watch2 = new Watcher();watch1.update();
watch1.update();
watch2.update();

我们现在 new 了两个 Watcher 对象,因为修改了 data 的数据,所以我们模拟触发了两次 watch1 的 update 以及 一次 watch2 的 update

假设没有批量异步更新策略的话,理论上应该执行 Watcher 对象的 run,那么会打印。

watch1 update
watch1视图更新啦~
watch1 update
watch1视图更新啦~
watch2 update
watch2视图更新啦~

实际上则执行

watch1 update
watch1 update
watch2 update
watch1视图更新啦~
watch2视图更新啦~

这就是异步更新策略的效果,相同的 Watcher 对象会在这个过程中被剔除,在下一个 tick 的时候去更新视图,从而达到对我们第一个例子的优化。

我们再回过头聊一下第一个例子, number 会被不停地进行 ++ 操作,不断地触发它对应的 Dep 中的 Watcher 对象的 update 方法。然后最终 queue 中因为对相同 id 的 Watcher 对象进行了筛选,从而 queue 中实际上只会存在一个 number 对应的 Watcher 对象。在下一个 tick 的时候(此时 number 已经变成了 1000),触发 Watcher 对象的 run 方法来更新视图,将视图上的 number 从 0 直接变成 1000。

到这里,批量异步更新策略及 nextTick 原理已经讲完了,接下来让我们学习一下 Vuex 状态管理的工作原理。

注:本节代码参考《批量异步更新策略及 nextTick 原理》。

上一篇:数据状态更新时的差异 diff 及 patch 机制下一篇:Vuex 状态管理的工作原理

批量异步更新策略及 nextTick 原理相关推荐

  1. vue 多个回调_Vue 进阶面试必问,异步更新机制和 nextTick 原理

    vue已是目前国内前端web端三分天下之一,同时也作为本人主要技术栈之一,在日常使用中知其然也好奇着所以然,另外最近的社区涌现了一大票vue源码阅读类的文章,在下借这个机会从大家的文章和讨论中汲取了一 ...

  2. 渲染篇四:千方百计——Event Loop 与异步更新策略

    千方百计--Event Loop 与异步更新策略 Vue 和 React 都实现了异步更新策略.虽然实现的方式不尽相同,但都达到了减少 DOM 操作.避免过度渲染的目的.通过研究框架的运行机制,其设计 ...

  3. vue如何让一句代码只执行一次_lt;Vue 源码笔记系列4gt;异步更新队列与$nextTick...

    1. 前言 原文发布在语雀: <Vue 源码笔记系列4>异步更新队列与$nextTick · 语雀​www.yuque.com 上一章我们讲到了修改数据是如何触发渲染函数的观察者,最终调用 ...

  4. “约见”面试官系列之常见面试题第三十九篇之异步更新队列-$nextTick(建议收藏)

    目录 一,前言 二,什么是异步更新队列 三,使用异步更新队列 四,结尾 一,前言 这一篇介绍有关异步更新队列的知识,通过异步更新队列的学习和研究能够更好的理解Vue的更新机制 二,什么是异步更新队列 ...

  5. 分布式系统中一些主要的副本更新策略——Dynamo/Cassandra/Riak同时采取了主从式更新的同步+异步类型,以及任意节点更新的策略。...

    分布式系统中一些主要的副本更新策略. 1.同时更新 类型A:没有任何协议,可能出现多个节点执行顺序交叉导致数据不一致情况. 类型B:通过一致性协议唯一确定不同更新操作的执行顺序,从而保证数据一致性 2 ...

  6. Redis-缓存更新策略

    Redis三种常见的缓存更新模式介绍 Redis常见的缓存更新策略有三种,分别是Cache Aside Pattern(旁路缓存模式).Read/Write Through Pattern(读写穿透模 ...

  7. DL:深度学习(神经网络)的简介、基础知识(神经元/感知机、训练策略、预测原理)、算法分类、经典案例应用之详细攻略

    DL:深度学习(神经网络)的简介.基础知识(神经元/感知机.训练策略.预测原理).算法分类.经典案例应用之详细攻略 目录 深度学习(神经网络)的简介 1.深度学习浪潮兴起的三大因素 深度学习(神经网络 ...

  8. [unity3d]手游资源热更新策略探讨

    原地址:http://blog.csdn.net/dingxiaowei2013/article/details/20079683 我们学习了如何将资源进行打包.这次就可以用上场了,我们来探讨一下手游 ...

  9. 缓存服务的更新策略有哪些?

    原文 在互联网项目开发中,缓存的应用是非常普遍了,缓存可以帮助页面提高加载速度,减少服务器或数据源的负载. 1.为什么需要缓存? 一般在项目中,最消耗性能的地方就是后端服务的数据库了.而数据库的读写频 ...

最新文章

  1. 大数据分布式集群搭建(插曲)
  2. java wav合并_用Java串联WAV文件
  3. 逗号分割符--字段中含逗号等情况的解析方法Java实现
  4. 变身抓重点小能手:机器学习中的文本摘要入门指南 | 资源
  5. python中gensim内没有summarization的问题
  6. 约束规划问题与凸二次规划
  7. 设计模式经典书籍推荐
  8. 微信小程序怎么开店?怎么开一个小程序店铺
  9. ShadowGun: Optimizing for Mobile Sample Level
  10. java的诞生詹姆斯·高斯林
  11. 计算机专硕学硕哪个好考啊,【专硕考研】计算机考研选学硕还是专硕?
  12. python 全栈什么意思_python全栈指的是什么意思
  13. 移动前端手机输入法自带emoji表情字符处理
  14. [原创]java实现word转pdf
  15. 解决Vue启动报错 npm ERR! @1.0.0 dev: node build/dev-server.js
  16. 南卡耳机和漫步者耳机哪个好?看完这篇文章就能知道哪个好
  17. 米尔基于ARM架构核心板的国产化EtherCAT主站控制器解决方案
  18. perl 处理 回车 换行符
  19. java制作管理系统视频_java语言制作管理系统视频教程
  20. 机器学习精髓-机器学习百页书

热门文章

  1. 苹果cmsv10播放器源码插件
  2. Win7+IIS7下用FastCGI模式配置PHP环境
  3. Bootstrap 3 : 图片上传预览 image upload preview
  4. PHP浮点运算结果出现误差原因分析及解决方案
  5. JavaScript判断浏览器 Browser detect
  6. wxPython中文教程 简单入门加实例
  7. 开启 Appserv 的 curl 功能
  8. HTML 5 canvas 基本语法
  9. mysql与python交互
  10. boost——windows下VS2013update5编译boost库