前言
这篇是对视图更新的性能优化,包含 nextTick 这一 api 的实现,涉及到 JS 事件循环相关概念。
目前的情况是 每次改变数据都会触发相应的 watcher 进行更新,数据每变动一次就会重新渲染一次,这样很浪费性能,所以我们要让数据全部变动完毕后再去统一更新视图
。
一、watcher 更新的改写
- 每次watcher进行更新的时候 是否可以让他们先缓存起来 之后再一起调用
- 异步队列机制
src/observer/watcher.js
把 update 更新方法改了,增加异步队列的机制
二、 queueWatcher 实现队列机制
新建 scheduler.js 文件 表示和调度相关, 先同步把 watcher 都放到队列里面去 ,执行完队列的事件之后再清空队列, 主要使用 nextTick 来执行 watcher 队列。
src/observer/scheduler.js
三、nextTick 实现原理
src/util/next-tick.js
MutationObserver()
构造函数 主要是监听dom变化 也是一个异步方法
js
// src/util/next-tick.js
let callbacks = [];
let pending = false;
function flushCallbacks () {
pending = false; // 把标志还原成 false
for (let i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
}
let timerFunc; // 定义异步方法,采用优雅降级
if (typeof Promise !== 'undefined') {
// 支持 promise
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
}
} else if (typeof MutationObserver !== 'undefined') {
// MutationObserver 主要是监听dom变化 也是一个异步方法
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter);
}
} else if (typeof setImmediate !== 'undefined') {
// 如果前面都不支持 判断 setImmediate
timerFunc = () => {
setImmediate(flushCallbacks);
}
} else {
// 最后降级采用 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0);
}
}
export function nextTick (cb) {
// 除了渲染 watcher,还有用户自己手动调用的 nextTick,一起收集到数组
callbacks.push(cb);
if (!pending) {
// 如果多次调用nextTick 只会执行一次异步 等异步队列清空之后再把标志变为false
pending = true;
timerFunc();
}
}
新建 util/next-tick.js 代表工具类函数 因为 nextTick 用户也可以手动调用 主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法
四、$nextTick 挂载原型
挂载在原型的nextTick方法 可供用户手动调用
src/render.js
最后把$nextTick 挂载到 Vue 的原型
最后效果
数据变化了 7 次,但是在数据变动完毕后再去更新视图