手摸手从0实现简版Vue ---(批量更新&nextTick)

接:javascript

手摸手从0实现简版Vue --- (对象劫持)vue

手摸手从0实现简版Vue --- (数组劫持)java

手摸手从0实现简版Vue --- (模板编译)git

手摸手从0实现简版Vue --- (依赖收集)github

1. 异步更新

假设咱们下面一种状况,咱们在2s后屡次去修改某一个变量的值:api

setTimeout(() => {
  vm.msg = '1';
  vm.msg = '2';
  vm.msg = '3';
  vm.msg = '4';
}, 2000);
复制代码

发现页面正常显示了msg的值,可是此时的数据更新致使的页面更新了4次,显然是不太合理的,咱们想要的是最终视图只更新一次,因此咱们须要将以前的同步更新逻辑修改为异步的。咱们首先将watcher放到一个数组中,首先用一个最简单的办法使用setTimeout在一轮任务执行结束后统一进行更新。数组

class Watcher {
  ... 
  update() {
    queueWatcher(this);
  }

  run() {
    console.log('数据更新');
    this.get()
  }
}

const queueIds = new Set();
let queue = [];
function flushQueue() {
  if (!queue.length) return;
  queue.forEach(watcher => watcher.run())
  queueIds.clear();
  queue = [];
}

function queueWatcher(watcher) {
  const id = watcher.id;
  if (!queueIds.has(id)) {
    queueIds.add(id);
    queue.push(watcher);
    setTimeout(flushQueue, 0);
  }
}
复制代码

这也就简单实现了视图的批量更新操做。浏览器

2. nextTick

此时的页面会统一进行一次刷新,可是Vue.$nextTick的实现并不仅仅是用setTimeout实现,nextTick内部一样也是维护了一个事件队列,等同步事件执行完毕后清空,就像咱们上面写到的queueWatcher同样,可是内部针对浏览器的api支持程度作了一些兼容和优化。异步

在异步队列中,微任务的优先级更高,因此优先使用Promise而不是setTimeout,另外还有几个异步的api,它们的优先级顺序分别是:post

  • Promise(微任务)
  • MutationObserver(微任务)
  • setImmediate(宏任务)
  • setTimeout(宏任务)
const callbacks = []

function flushCallbacks() {
  callbacks.forEach(cb => cb())
}

export default function nextTick(cb) {
  callbacks.push(cb)

  const timerFunc = () => {
    flushCallbacks()
  }

  if (Promise) {
    return Promise.resolve().then(flushCallbacks)
  }

  if (MutationObserver) {
    const observer = new MutationObserver(timerFunc)
    const textNode = document.createTextNode('1')
    observer.observe(textNode, { characterData: true })
    textNode.textContent = '2'
    return
  }

  if (setImmediate) {
    return setImmediate(timerFunc)
  }

  setTimeout(timerFunc, 0)
}
复制代码

而后将以前的setTimeout替换成nextTick看一下效果:

function queueWatcher(watcher) {
  const id = watcher.id;
  if (!queueIds.has(id)) {
    queueIds.add(id);
    queue.push(watcher);
+    nextTick(flushQueue, 0)
-    setTimeout(flushQueue, 0);
  }
}
复制代码

效果相同,视图也只更新了一次,实现了视图的异步批量更新。

到如今为止的话,咱们针对响应式原理已经作了基本功能的实现,后面咱们会去对computedwatch进行一下简单的模拟。

代码部分可看本次提交commit

相关文章
相关标签/搜索