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

994 阅读2分钟

接:

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

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

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

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

1. 异步更新

假设我们下面一种情况,我们在2s后多次去修改某一个变量的值:

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,它们的优先级顺序分别是:

  • 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