Vue 源码初探(五)对象异步更新nextTick()

400 阅读2分钟

思维导图

image.png

前言

上一节我们实现了当我们改变了属性值之后页面会自动触发更新的流程,本节我们要考虑的是,当我们多次修改同一个属性的时候避免渲染多次的情况发生,所以我们在内部采用了异步更新页面的流程。

正文

举例

var vm = new Vue({
  el: '#app',
  data: {
    name: '小明',
    age: 20
  }
})

vm.name = '天明'
vm.name = '天明1'
vm.name = '天明2'
vm.name = '天明3'

watcher.js

update() {
    //会多次更新
    this.get()
}

我们可以看见这个时候重新渲染页面的函数被执行了四次,vue在实现的时候为了考虑页面的性能,就在内部做了一定的优化。下面让我们一起来看看吧。 image.png

批量异步渲染

watcher.js

class Watcher {
 //...
  update() {
    //会多次更新
    queueWatcher(this)
  }
  run() {
    console.log('update')
    this.get()
  }
}

+scheduler.js

import { nextTick } from "../utils"

let queue = []  //存储要更新的watcher 
let has = {}    //检测有没有重复的watcher
let pending = false

function flushSchedulerQueue() {
  queue.forEach(watcher => watcher.run())
  queue = []
  has = {}
  pending = false
}

export function queueWatcher(watcher) {
  let id = watcher.id
  if (has[id] === undefined) {
    has[id] = true
    queue.push(watcher)
    if (!pending) {
      nextTick(flushSchedulerQueue)
      pending = true
    }
  }
}

$nextTick

init.js

Vue.prototype.$nextTick = nextTick

vm.$nextTink与异步更新在一个微任务中

util.js

// 之前是每一个异步更新都开启一个promise微任务,现在要优化成多个异步更新只开启一个微任务
let callbacks = []
let waiting = false

//批量执行,同步代码都执行完毕之后才会执行。
function flushCallbacks() {
  callbacks.forEach(fn => fn())
  callbacks = []
  waiting = false
}

export function nextTick(fn) {
  callbacks.push(fn)
  if(!waiting) {
    Promise.resolve().then(flushCallbacks)
    waiting = true
  }
}

//多个nextTick()是一次 promise的then()

小结

本节主要是对象属性改变之后的异步更新处理,下一节主要写数组的依赖收集。

系列文章链接(持续更新中...)

Vue 源码初探(一)响应式原理

Vue 源码初探(二)模板编译

Vue 源码初探(三)单组件挂载(渲染)

Vue 源码初探(四)依赖(对象)收集的过程

Vue 源码初探(五)对象异步更新nextTick()