接:javascript
手摸手从0实现简版Vue --- (依赖收集)github
假设咱们下面一种状况,咱们在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);
}
}
复制代码
这也就简单实现了视图的批量更新操做。浏览器
此时的页面会统一进行一次刷新,可是Vue.$nextTick
的实现并不仅仅是用setTimeout
实现,nextTick内部一样也是维护了一个事件队列,等同步事件执行完毕后清空,就像咱们上面写到的queueWatcher同样,可是内部针对浏览器的api支持程度作了一些兼容和优化。异步
在异步队列中,微任务的优先级更高,因此优先使用Promise而不是setTimeout,另外还有几个异步的api,它们的优先级顺序分别是:post
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);
}
}
复制代码
效果相同,视图也只更新了一次,实现了视图的异步批量更新。
到如今为止的话,咱们针对响应式原理已经作了基本功能的实现,后面咱们会去对computed
和watch
进行一下简单的模拟。
代码部分可看本次提交commit