关于javascript:Vue011版本源码阅读系列五批量更新是怎么做的

0次阅读

共计 3297 个字符,预计需要花费 9 分钟才能阅读完成。

在第三篇 vue0.11 版本源码浏览系列三:指令编译里咱们晓得如果某个属性的值变动了,会调用依赖该属性的 watcherupdate办法:

p.update = function () {if (!config.async || config.debug) {this.run()
  } else {batcher.push(this)
  }
}

它没有间接调用指令的 update 办法,而是交给了 batcher,本篇来看一下这个batcher 做了什么。

顾名思义,batcher是批量的意思,所以就是批量更新,为什么要批量更新呢,先看一下上面的状况:

<div v-if="show"> 我进去了 </div>
<div v-if="show && true"> 我也是 </div>
window.vm.show = true
window.vm.show = false

比方有两个指令依赖同一个属性或者间断批改某个属性,如果不进行批量异步更新,那么就会屡次批改 dom,这显然是没必要的,看上面两个动图能更直观的感触到:

没有进行批量异步更新的时候:

进行了批量异步更新:

能清晰的发现通过异步更新能跳过两头不必要的渲染以达到优化性能的成果。

接下来看一下具体实现,首先是 push 函数:

// 定义了两个队列,一个用来寄存用户的 watcher,一个用来寄存指令更新的 watcher
var queue = []
var userQueue = []
var has = {}
var waiting = false
var flushing = false
exports.push = function (job) {
  // job 就是 watcher 实例
  var id = job.id
  // 在没有 flushing 的状况下 has[id]用来跳过同一个 watcher 的反复增加
  if (!id || !has[id] || flushing) {has[id] = 1
    // 首先要阐明的是通过 $watch 办法或者 watch 选项生成的 watcher 代表是用户的,user 属性为 true
    // 这里正文说在执行工作中用户的 watcher 可能会触发非 user 的指令更新,所以要立刻更新这个被触发的指令,否则 flushing 这个变量是不须要的
    if (flushing && !job.user) {job.run()
      return
    }
    // 依据指令的类型增加到不同的队列里
    ;(job.user ? userQueue : queue).push(job)
    // 上个队列未被清空前不会创立新队列
    if (!waiting) {
      waiting = true
      _.nextTick(flush)
    }
  }
}

push办法做的事件是把 watcher 增加到队列 quene 里,而后如果没有扔过 flushnextTick或者上次扔给 nextTickflush办法曾经被执行了,就再给它一个。

flush办法用来遍历队列里的 watcher 并调用其 run 办法,run办法最终会调用指令的 update 办法来更新页面。

function flush () {
  flushing = true
  run(queue)
  run(userQueue)
  // 清空队列和复位变量
  reset()}
function run (queue) {
  // 循环执行 watcher 实例的 run 办法,run 办法里会遍历该 watcher 实例的指令队列并执行指令的 update 办法
  for (var i = 0; i < queue.length; i++) {queue[i].run()}
}

接下来就是 nextTick 办法了:

exports.nextTick = (function () {var callbacks = []
  var pending = false
  var timerFunc
  function handle () {
    pending = false
    var copies = callbacks.slice(0)
    callbacks = []
    for (var i = 0; i < copies.length; i++) {copies[i]()}
  }
  // 反对 MutationObserver 接口的话应用 MutationObserver
  if (typeof MutationObserver !== 'undefined') {
    var counter = 1
    var observer = new MutationObserver(handle)
    var textNode = document.createTextNode(counter)
    observer.observe(textNode, {characterData: true// 设为 true 示意监督指定指标节点或子节点树中节点所蕴含的字符数据的变动})
    timerFunc = function () {counter = (counter + 1) % 2// counter 会在 0 和 1 两者循环变动
      textNode.data = counter// 节点变动会触发回调 handle,}
  } else {// 否则应用定时器
    timerFunc = setTimeout
  }
  return function (cb, ctx) {
    var func = ctx
      ? function () { cb.call(ctx) }
      : cb
    callbacks.push(func)
    if (pending) return
    pending = true
    timerFunc(handle, 0)
  }
})()

这是个自执行函数,个别用来定义并保留一些局部变量,返回了一个函数,就是 nextTick 办法本法了,flush办法会被 pushcallbacks数组里,咱们罕用的办法 this.$nextTick(() => {xxxx}) 也会把回调增加到这个数组里,这里也有一个变量 pending 来管制反复增加的问题,最初增加到事件循环的队列里的是 handle 办法。

批量很容易了解,都放到一个队列里,最初一起执行就是批量执行了,然而要了解 MutationObserver 的回调或者 setTimeout 的回调为什么能异步调用就须要先来理解一下 JavaScript 语言里的事件循环 Event Loop 的原理了。

简略的说就是因为 JavaScript 是单线程的,所以工作须要排队进行执行,前一个执行完了能力执行前面一个,但有些工作比拟耗时而且没必要等着,所以能够先放一边,先执行前面的,等到了能够执行了再去执行它,比方有些 IO 操作,像常见的鼠标键盘事件注册、Ajax申请、settimeout定时器、Promise回调等。所以会存在两个队列,一个是同步队列,也就是主线程,另一个是异步队列,方才提到的那些事件的回调如果能够被执行了都会被放在异步队列里,当主线程上的工作执行结束后会把异步队列的工作取过去进行执行,所以同步代码总是在异步代码之前执行,执行完了后又会去查看异步队列,这样一直循环就是Event Loop

然而异步工作里其实还是分两种,一种叫宏工作,常见的为:setTimeoutsetInterval,另一种叫微工作,常见的如:PromiseMutationObserver。微工作会在宏工作之前执行,即便宏工作的回调先被增加到队列里。

当初能够来剖析一下异步更新的原理,就以结尾提到的例子来说:

<div v-if="show"> 我进去了 </div>
<div v-if="show && true"> 我也是 </div>
window.vm.show = true
window.vm.show = false

因为有两个指令都依赖了 show,表达式不一样,所以会有两个watcher,这两个watcher 都会被 show 属性的 dep 收集,所以每批改一次 show 的值都会触发这两个 watcher 的更新,也就是会调两次 batcher.push(this) 办法,第一次调用后会执行 _.nextTick(flush) 注册一个回调,间断两次批改 show 的值,会调用四次上述提到的 batcher.push(this) 办法,因为反复增加的被过滤掉了,所以最初会有两个 watcher 被增加到队列里,以上这些操作都是同步工作,所以是间断被执行完的,等这些同步工作都被执行完了后就会把方才注册的回调 handle 拿过去执行,也就是会一次性执行方才增加的两个watcher

以上就是 vue 异步更新的全部内容。

正文完
 0