接: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