首先我们先了解下Vue的视图更新机制,可以总结为以下流程
1.数据改变 --> 2.生成新的 virtual dom --> 3.新旧 virtual dom 对比 patch --> 4.真实dom更新
思考以下代码
new Vue({
el: '#example',
data(){
return{
num:1
}
},
methods: {
changeData: function () {
for (let i = 0; i < 100; i++) {
this.num += 1
}
}
}
})
根据我们上面总结的 vue 视图更新机制,那么执行 changeData时,将会触发 100 次视图更新流程(步骤2 到步骤5),显然易见,这会导致很大的性能问题,所以 vue 内部使用了 异步更新队列来优化。
异步更新队列
- 数据更新不会立即触发新的
virtual dom生成以及diff patch,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据变更 - 下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
自己实现一个简单的异步更新队列
以下代码会执行100次 setState 方法 ,而每次 执行 setState将会调用一次 render函数,实际情况中,render函数将会包含大量的计算(diff、 patch),我们要做的优化就是将 render函数的执行次数降低 为 1 次
const state = {
num: 1
}
for (let i = 0; i < 100; i++) {
setState({
num: num + 1
})
}
function setState(stateChange) {
Object.assign(state, stateChange)
render(state)
}
function render(state) {
console.log('state 的值是:', state);
}
源码
const state = {
num: 1
}
const setStateQueue = [];//状态改变队列,每次状态改变时候将 stateChange push 进来
for (let i = 0; i < 100; i++) {
setState({
num: state.num + 1
})
}
function setState(stateChange) {
enqueueSetState(stateChange)
}
/*
在 Vue 2.4 之前都是使用的 microtasks,
但是 microtasks 的优先级过高,在某些情况下可能会出现比事件冒泡更快的情况,
但如果都使用 macrotasks 又可能会出现渲染的性能问题。
所以在新版本中,会默认使用 microtasks,但在特殊情况下会使用 macrotasks,比如 v-on。
为了简单起见, 我们先使用 promise 来生成 microtasks
*/
function defer(fn) {
return Promise.resolve().then(fn);
}
function enqueueSetState(stateChange) {
if (setStateQueue.length === 0) {
defer(flush)
}
setStateQueue.push({ stateChange });
}
function flush() {
let item
while (item = setStateQueue.shift()) { //执行队列内的每一次数据变更
const { stateChange } = item;
Object.assign(state, stateChange);
}
render(state)
}
function render(state) {
console.log('render 函数中获取 state 的值是:', state);
}
执行下看看,render 确实只执行了一次
