异步更新队列
在上面 Vue2.x 响应式原理的代码里还是有点问题的。
先捞一遍代码:
let x;
let f = function (v) {
return v * 100;
};
let active;
const onXChange = (cb) => {
active = cb;
active();
active = null;
};
class Dep {
deps = new Set();
depend() {
if (active) {
this.deps.add(active);
}
}
notify() {
this.deps.forEach((dep) => dep());
}
}
const ref = (value) => {
let initValue = value;
let deps = new Dep();
return Object.defineProperty({}, "value", {
get() {
deps.depend();
return initValue;
},
set(newVal) {
initValue = newVal;
deps.notify();
},
});
};
x = ref(1);
onXChange(() => {
console.log(f(x.value));
});
x.value = 2;
x.value = 3;
依赖多个变量的情况下的性能问题
简单删减一波,然后让模板依赖于三个变量:
let active;
const watch = (cb) => {
active = cb;
active();
active = null;
};
class Dep {
deps = new Set();
depend() {
if (active) {
this.deps.add(active);
}
}
notify() {
this.deps.forEach((dep) => dep());
}
}
const ref = (value) => {
let initValue = value;
let deps = new Dep();
return Object.defineProperty({}, "value", {
get() {
deps.depend();
return initValue;
},
set(newVal) {
initValue = newVal;
deps.notify();
},
});
};
let x = ref(1);
let y = ref(2);
let z = ref(3);
watch(() => {
let str = `${x.value} --- ${y.value} --- ${z.value}`;
document.write(str);
console.log(str);
});
x.value = 2;
y.value = 3;
z.value = 3;
打开控制台你会发现,每一次值变化的时候,都会触发一次onXChange。
那么问题来了。如果在一个函数中,修改了多个变量,那岂不是页面就要进行多次渲染吗?那么性能肯定下降了。
那么这怎么办呢。
异步更新队列
那么就得用到异步更新队列了。
我们知道 JavaScript 有宏任务和微任务。所有的微任务都是在宏任务执行完成之后再执行的。
那么我们可以定义一个nextTick函数,用来在宏任务执行完成之后执行微任务。 即,在所有的赋值完成之后,再执行onChange方法。
const netxtTick = (cb) => Promise.resolve().then(cb);
我们还需要一个队列用来保证先进来的onChange先执行:
let queue = [];
// 增加任务的方法
let queueJob = (job) => {
// 如果任务已经被添加过,则不添加了
if (!queue.includes(job)) {
queue.push(job);
// 在微任务中执行所有回调
nextTick(flushJobs);
}
};
// 执行任务的方法
let flushJobs = () => {
let job;
// 如果队列中第一个始终有任务,则取出来执行
while ((job = queue.shift()) !== undefined) {
job();
}
};
整体代码如下:
let active;
const watch = (cb) => {
active = cb;
active();
active = null;
};
let nextTick = (cb) => Promise.resolve().then(cb);
let queue = [];
let queueJob = (job) => {
if (!queue.includes(job)) {
queue.push(job);
nextTick(flushJobs);
}
};
let flushJobs = () => {
let job;
while ((job = queue.shift()) !== undefined) {
job();
}
};
class Dep {
deps = new Set();
depend() {
if (active) {
this.deps.add(active);
}
}
notify() {
// 注意notify内部变更了
this.deps.forEach((dep) => queueJob(dep));
}
}
const ref = (value) => {
let initValue = value;
let deps = new Dep();
return Object.defineProperty({}, "value", {
get() {
deps.depend();
return initValue;
},
set(newVal) {
initValue = newVal;
deps.notify();
},
});
};
let x = ref(1);
let y = ref(2);
let z = ref(3);
watch(() => {
let str = `${x.value} --- ${y.value} --- ${z.value}`;
document.write(str);
console.log(str);
});
x.value = 2;
y.value = 3;
z.value = 3;