vue源码解读(四)Vue中的异步更新策略

欢迎star我的github仓库,共同学习~目前vue源码学习系列已经更新了6篇啦~

https://github.com/yisha0307/...

快速跳转:

异步更新队列

引用下vue.js官方教程关于vue中异步更新队列的解释:

可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

为什么要这么做呢?来看一个简单的例子:

<template>
  <div>
    <div>{{test}}</div>
  </div>
</template>
export default {
    data () {
        return {
            test: 0
        };
    },
    mounted () {
      for(let i = 0; i < 1000; i++) {
        this.test++;
      }
    }
}

根据mounted里的代码,test一共被触发了1000次,如果不是异步更新,watcher在被触发1000次时,每次都会去更新视图,这是非常非常消耗性能的。因此,vue才会采用异步更新的操作,如果同一个 watcher 被多次触发,只会被推入到队列中一次。然后在下一次事件循环'tick‘的时候,只需更新一次dom即可。

我们来看一下watcher类中的update方法,了解下具体这个异步更新代码是如何实现的。

update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      /*同步则执行run直接渲染视图*/
      // 基本不会用到sync
      this.run()
    } else {
      /*异步推送到观察者队列中,由调度者调用。*/
      queueWatcher(this)
    }
  }
export function queueWatcher (watcher: Watcher) {
  /*获取watcher的id*/
  const id = watcher.id
  /*检验id是否存在,已经存在则直接跳过,不存在则标记哈希表has,用于下次检验*/
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      /*如果没有flush掉,直接push到队列中即可*/
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i >= 0 && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(Math.max(i, index) + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}

我们可以看到,watcher在update时候,调用了一个queueWatcher的方法,将watcher异步推送到观察者队列中;调用queueWatcher的时候,首先查看了watcher的id,保证相同的watcher不被多次推入;waiting是一个标识,如果没有在waiting, 就异步执行flushSchedulerQueue方法。

flushSchedulerQueue

flushSchedulerQueue是下一个tick的时候的回调函数,主要就是去执行watcher中的run方法。看一下源码:

/*nextTick的回调函数,在下一个tick时flush掉两个队列同时运行watchers*/
function flushSchedulerQueue () {
  flushing = true
  let watcher, id

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  /*
    给queue排序,这样做可以保证:
    1.组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。
    2.一个组件的user watchers比render watcher先运行,因为user watchers往往比render watcher更早创建
    3.如果一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过。
  */
 // queue: Array<Watcher>
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    id = watcher.id
    /*将has的标记删除*/
    has[id] = null
    /*执行watcher*/
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }

  // keep copies of post queues before resetting state
  /**/
  /*得到队列的拷贝*/
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  /*重置调度者的状态*/
  resetSchedulerState()

  // call component updated and activated hooks
  /*使子组件状态都改编成active同时调用activated钩子*/
  callActivatedHooks(activatedQueue)
  /*调用updated钩子*/
  callUpdateHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}

操作真实DOM

这时候我们再来看一下vue中文教程里的代码:

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

当设置vm.message = 'new message'时候,该组件并不会立即进行渲染,而是在dep.notify了watcher之后,被异步推到了调度者队列中,等到nextTick的时候进行更新。nextTick则是会去尝试原生的Promise.then和MutationObserver,如果都不支持,最差会采用setTimeout(fn, 0)进行异步更新。所以如果想基于更新后的DOM状态做点什么,可以在数据变化之后立即使用Vue.nextTick(callback)对真实DOM进行操作。

Image placeholder
goly668
未设置
  81人点赞

没有讨论,发表一下自己的看法吧

推荐文章
记一次 vue 的异步更新队列导致内存泄漏

起因 由于项目是需要连续传输图片形成一个伪视频(没办法,客户钱给的不够)来观看。后端采用传输base64的图片到前端展示。 环境 php:7.2 workerman:3.X vue:2.X 过程 wo

HDFS 源码解读:HadoopRPC 实现细节的探究

桔妹导读:HDSF作为分布式文件系统,常常涉及DataNode、NameNode、Client之间的配合、相互调用才能完成完整的流程。为了降低节点之间的耦合性,HDFS将节点间的调用抽象成不同的接口,

Spring Cloud Alibaba 教程 | Nacos(四)

Nacos环境隔离Nacos管理台有一个单独的菜单“命名空间”,里面默认存在一个名为“public”的默认命名空间,我们在使用Nacos时不管是作为注册中心还是配置中心,都是作用在该命名空间之下的,那

vue中的diff算法详解

1.当数据发生变化时,vue是怎么更新节点的?要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修改的

echarts怎么引入到vue中?

准备工作:首先我们初始化一个vue项目,执行vueinitwebpackechart。接着我们进入初始化的项目下,安装echarts,npminstallecharts-S //或 cnpminsta

axios怎么引入到vue中?

Axios是一个基于promise的HTTP库,可以用在浏览器和node.js中。在vue项目之中使用axios是一个非常明智的选择,因为vue官方已经宣称不再维护vue-resource,并且推荐使

vue中如何关闭eslint检测?

vue中如何关闭eslint检测?有了eslint的校验,可以来规范开发人员的代码,是挺好的。但是有些像缩进、空格、空白行之类的规范,在开发过程中一直报错,未免太过于苛刻了。所以,下面来介绍下怎么在v

ajax怎么在vue中使用?

ajax怎么在vue中使用?axios是一个基于Promise的HTTP请求客户端,用来发送请求,也是vue2.0官方推荐的,同时不再对vue-resource进行更新和维护本文为大家介绍vue使用a

理解Vue中间件管道

通常,在构建SPA时,需要保护某些路由。例如假设有一个只允许经过身份验证的用户访问的dashboard路由,我们可以通过使用auth中间件来确保合法用户才能访问它。在本教程中,我们将学到怎样用Vue-

【源码解析】扒开ArrayList的外衣

积千里跬步,汇万里江河;每天进步一点点,终有一天将成大佬。本文内容当然ArrayList里的方法不止这些,本文主要讲一些常用的方法方法变量Arraylist里的方法变量主要有以下几个1.构造方法1.1

node中文什么意思?

起初,RyanDahl称他的项目为web.js,就是一个Web服务器,但是项目的发展超过了他最初单纯开发一个Web服务器的想法,变成了构建网络应用的一个基础框架,这样可以在它的基础上构建更多的东西,诸

react-native中IOS的webview和js层通信 - UIWebview

前言在9012的最后一篇写到了在rn中安卓的webview的通信原理,而作为0202年的第一篇,继续讨论上年rn中webview通信剩下的部分。背景:对于webview,了解过的人都知道在ios端会存

leveldb源代码分析系列1.1:memtable中comparator的实现

leveldb中memtable封装了一个skiplist用来存储真正的数据,跳跃列表的实现一定需要定义存储项的序关系,而在leveldb中这个序关系通过comparator相关类来实现。leveld

vue加载优化策略有哪些?

方法一路由懒加载vue.js是一个比较流行的前端框架,与react.js、angular.js相比来说,vue.js入手曲线更加流畅,不管掌握多少都可以快速上手。但是单页面应用也都有其弊病,有时候首屏

jQuery与Zepto的异同

jQuery与Zepto的异同一、同Zepto最初是为移动端开发的库,是jQuery的轻量级替代品,因为它的API和jQuery相似,而文件更小。Zepto最大的优势是它的文件大小,只有8k多,是目前

深度解读当代前端架构演进与趋势(上)

软件架构的核心思想,就是推断软件系统各个组件之间数据流动的方式。软件架构的质量取决于你设法推断这些数据流的难易程度!本文要讲的内容,就是在今天的Web应用程序背后探索这些数据流和最终的体系结构。We

Oracle SCN机制详细解读

深入剖析–OracleSCN机制详细解读http://blog.chinaunix.net/uid-20274021-id-1969571.htmlSCN即系统改变号(SystemChangeNumb

解读 KubeCon EU 2019 应用管理领域的新看点

作者 |阿里云智能事业群技术专家邓宏超划重点阿里云容器平台技术专家、原CoreOS公司工程师、K8sOperator项目的核心作者之一邓洪超,精彩解读KubeConEU2019“应用管理“领域精华内容

中国移动智能硬件质量报告解读 分布式路由市场你了解多少?

今年6月份,中国移动终端实验室发布了《中国移动2019年智能硬件质量报告》(第一期),并于近日对该报告进行了相关解读,同时对优秀智能硬件产品进行颁奖。根据介绍,本次报告在内容上主要包括手机产品综合评测

微服务配置中心完全解读

本文作者:风卿,Nacos社区committer.在撰写这篇技术选型的文章之前,是比较犹豫的。因为,以其中一个开源项目开发者的身份,去写一篇三个开源项目的对比,即便很克制的去客观的比较,也很难有信服力

解读2019华为第001号文件:AI时代软件开发的第一要义是可信

晓查发自凹非寺量子位出品|公众号QbitAIAI加持,万物互联、万物智能。我们在享受科技进步的同时,软件开发行业却面临着更大的挑战。过去,软件出现安全问题或许仅仅意味着经济损失,但当走向产业互联网时代

《Gartner 2019年数据中心网络魔力象限》报告解读

日前,Gartner发布了2019年数据中心网络魔力象限。此次报告从7个维度(产品或服务、总体生存能力、销售执行/定价、市场反应/记录、营销执行、客户体验和操作),来衡量12个入选企业,并基于前瞻性和

开创万兆组网时代 新华三商用万兆解决方案解读

随着网络技术的不断发展,越来越多的终端设备纷纷接入网络,用户对于高速网络的需求越来越高。网络速率也从10兆到百兆再到千兆一步步得以提升。我们享受了高速网络所带来的极大便利,但这些还远远不够……由于物联

助力平安城市 新华三安防监控解决方案解读

大家应该都有看过警匪片,在影视片段中警察通过视频监控能够对城市的各个角落进行全局监控,并对犯罪分子进行实时追踪。为城市治安防控以及案件侦破提供了极大便利。而在现实生活中,视频监控系统也的确有着非常重要

分层存储超详细解读,为什么大数据时代它已不可或缺

如今,分层存储已成为了一种常见的存储方法,它将数据存储在具有不同特性(如性能、成本和容量)的不同存储介质上。不同的存储媒介被分配到不同的层次结构中,其中最高性能的存储媒介被认为是第0层或第1层,然后是