主题
异步跟新
问题说明
上一篇已经完成了依赖收集与派发跟新, 但是存在一个问题就是, 跟新是同步的, 也就是当在页面中多次调用vm.xxxx=xxx
时候, 每调用一次都会更新一次页面
更新页面是通过updateComponent
完成的(也就是执行_render
函数) ,即watcher
中的get
函数,而更新的逻辑是同步代码,所以为同步更新
异步更新队列
js
import { popTarget, pushTarget } from "./dep";
import { queueWatcher } from "./scheduler";
let uid = 0;
class Watcher {
constructor(vm, exprFn, cb, options) {
this.vm = vm;
this.exprFn = exprFn; // 组件真是的渲染函数
this.cb = cb; // 回调函数
this.options = options; // 是否为渲染watcher
this.uid = uid++; // 每一个组件都有一个渲染watcher, 这是唯一标识
this.depsIds = new Set(); // 一个watcher上面有多个dep, 且不重复
this.deps = [];
this.get(); // 首先就让组件渲染函数执行
}
run() {
this.get();
}
// 添加dep
addDep(dep) {
const depId = dep.uid;
if (!this.depsIds.has(depId)) {
this.depsIds.add(depId);
this.deps.push(dep); // 将当前watcher与dep建立1对多的关系
dep.subs.push(this); // dep与watcher也建立1对多的关系
}
}
// 更新页面
update() {
queueWatcher(this);
}
get() {
pushTarget(this);
this.exprFn();
popTarget();
}
}
export default Watcher;
schedule
js
import { nextTick } from "../utils/index";
let queue = [];
const watcherIds = new Set();
let warting = false;
export function queueWatcher(watcher) {
if (!watcherIds.has(watcher.uid)) {
queue.push(watcher);
watcherIds.add(watcher.uid);
}
if (warting === false) {
warting = true;
nextTick(flushQueue);
}
}
// 清空渲染队列
function flushQueue() {
queue.forEach((w) => w.run());
queue = [];
watcherIds.clear();
warting = false;
}
nextTick
异步跟新的核心就是nextTick函数, 在浏览器事件循环中 ,异步任务执行时机总在同步任务执行完后才会执行, 异步任务又存在宏任务与微任务,微任务的优先级比宏任务高
Vue2中的nextTick函数做了很多兼容, 依次是 Promise -> MutationObserve -> setImmediate -> setTimeout
js
// nextTick
let callbackQueue = [];
let pending = false;
function flushCallBackQueue() {
callbackQueue.forEach((cb) => cb());
callbackQueue = [];
pending = false;
}
let timerFunc;
if (Promise) {
timerFunc = () => {
Promise.resolve().then(flushCallBackQueue);
};
} else if (MutationObserver) {
let mutationObserve = new MutationObserver(flushCallBackQueue);
let text = document.createTextNode("1");
mutationObserve.observe(text, { characterData: true });
timerFunc = () => {
text.data = "2";
};
// @ts-ignore
} else if (typeof setImmediate === "object") {
timerFunc = () => {
// @ts-ignore
setImmediate(flushCallBackQueue);
};
} else {
timerFunc = setTimeout(flushCallBackQueue);
}
// nextTick方法永不会调用, 内部也会调用, 应该有先后顺序, 因此需要用队列进行管理
export function nextTick(cb) {
callbackQueue.push(() => {
cb();
});
console.log(callbackQueue);
if (pending === false) {
pending = true;
timerFunc();
}
}
vm.$nextTick(cb)
js
// init.js
import { mergeOptions, nextTick } from "./utils/index";
export function initMixin(Vue) {
// Vue.prototype._init = .....
// Vue.prototype.$mount = ....
Vue.prototype.$nextTick = nextTick;
}