問題:如代碼所示脐嫂,我們在多次更新data屬性统刮,會不會觸發(fā)多次dom diff計算和渲染,在更新data屬性到dom渲染過程發(fā)生了什么事账千?
<template>
<div>
<div>{{title}}</div>
<div @click="refresh">更新</div>
</div>
</template>
<script>
export default {
data() {
return {
title: ''
};
},
methods: {
refresh() {
this.title = '測試數(shù)據(jù)1';
this.title = '測試數(shù)據(jù)2';
this.title = '測試數(shù)據(jù)3';
this.title = '測試數(shù)據(jù)4';
this.title = '測試數(shù)據(jù)5';
this.title = '測試數(shù)據(jù)6';
}
}
};
</script>
一侥蒙、了解Observer、Dep匀奏、Watcher
1鞭衩、vue組件初始化
vue組件初始化的時候會執(zhí)行_init函數(shù),從而做了一些初始化處理娃善,在處理不同階段中調(diào)用不同的vue生命周期函數(shù)论衍,例:breforeCreate、created等
Vue.prototype._init = function (options) {
var vm = this;
initEvents(vm);
// 將render函數(shù)轉(zhuǎn)化為vnode
initRender(vm);
// 調(diào)用beforeCreate生命周期函數(shù)
callHook(vm, 'beforeCreate');
initInjections(vm);
// 初始化處理props聚磺、methods坯台、data、computed瘫寝、watch
initState(vm);
initProvide(vm);
// 調(diào)用created生命周期函數(shù)
callHook(vm, 'created');
....
// 掛載組件到DOM樹
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
2蜒蕾、Observer
initState → initData → observe → new Observer(data) → Observer.walk(data) → defineReactive$$1(data, key) → Object.defineProperty(data, key, {get, set})
通過調(diào)用鏈可以發(fā)現(xiàn)在initData開始,由Observer開始對data每個key值進行set焕阿、get的攔截監(jiān)聽(利用Object.defineProperty)咪啡,同時可以發(fā)現(xiàn)在defineReactive$$1中對每個key值的攔截監(jiān)聽都會創(chuàng)建一個Dep對象,在get的時候調(diào)用dep.depend()暮屡,在set的時候調(diào)用dep.notify()
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
// 對data的key進行set撤摸、get攔截
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
dep.depend();
},
set: function reactiveSetter (newVal) {
dep.notify();
}
}
}
3、Dep
Dep對象主要將watcher對象維護在自己的屬性中栽惶,同時也將自己加入到watcher中愁溜,在notify時候會調(diào)用watcher的update方法
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
// 將watcher加入到dep的subs數(shù)組中
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
// 將dep加入到watcher的deps數(shù)組中、同時
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
// 遍歷dep中watcher外厂、調(diào)用他們update方法
Dep.prototype.notify = function notify () {
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
4、Watcher
vm.$mount → mountComponent → new Watcher(vm, updateComponent)
由調(diào)用鏈在創(chuàng)建Watcher對象后代承,會將自己壓入棧頂汁蝶,等待觸發(fā)data 屬性getter時候,將dep和watcher對象互相綁定
// 掛載組件
function mountComponent (
vm,
el,
hydrating
) {
updateComponent = function () {
// vm.render()執(zhí)行render函數(shù)生成新節(jié)點
vm._update(vm._render(), hydrating);
};
// 創(chuàng)建Watcher對象
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true);
}
/************* Watcher函數(shù) **********************/
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
// 存儲的dep數(shù)組
this.deps = [];
// 存儲的dep id
this.depIds = new _Set();
// 存儲調(diào)用者傳過來的updateComponent
this.expression = expOrFn.toString();
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
}
this.value = this.lazy ? undefined : this.get();
}
// watcher的get方法
Watcher.prototype.get = function get () {
// 將當前watcher壓棧
pushTarget(this);
value = this.getter.call(vm, vm);
// 將當前watcher出棧
popTarget();
}
// 添加dep到Watcher對象屬性中
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
// 將watcher對象添加到dep對象中
dep.addSub(this);
}
}
};
// Watcher對象update方法
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
// 懶加載
this.dirty = true;
} else if (this.sync) {
// 同步組件
this.run();
} else {
// watcher隊列
queueWatcher(this);
}
};
// Watcher對象run方法
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get();
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
this.value = value;
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else {
this.cb.call(this.vm, value, oldValue);
}
}
}
};
二论悴、vue組件中數(shù)據(jù)更新會發(fā)生什么事情掖棉?
由于上面我們同步了一些關(guān)鍵信息,通過斷點結(jié)合膀估,可以看到整個調(diào)用過程
1幔亥、數(shù)據(jù)更新,我們首先會觸發(fā)Data的set攔截察纯,同時調(diào)用dep的notify方法帕棉。
2针肥、遍歷dep對象中的watcher數(shù)組,執(zhí)行watcher的update方法香伴。
3慰枕、在watcher的update函數(shù)中調(diào)用queueWatcher方法,并將自己作為入?yún)ⅰ?br>
4即纲、在queueWatcher函數(shù)中具帮,判斷has map是否有當前watcher id,沒有則將watcher存入queue隊列低斋,記錄watcher id到has map中蜂厅,調(diào)用nextTick方法;有則跳過這一步膊畴,等待異步刷新隊列執(zhí)行(異步刷新隊列:flushSchedulerQueue)晚碾。
5落午、在nextTick中,將flushSchedulerQueue push進callbacks數(shù)組,執(zhí)行timerFunc方法啡专。
6、timerFunc中等待瀏覽器的微任務/宏任務回調(diào)時候遍歷執(zhí)行callbacks數(shù)組中的異步刷新隊列方法flushSchedulerQueue抱怔。
7窿春、在flushSchedulerQueue中,調(diào)用watcher的run方法政勃。
8唧龄、在watcher的run中,調(diào)用watcher的get方法奸远。
9既棺、在watcher的get中,調(diào)用watcher的getter屬性方法懒叛。
10丸冕、在Watcher對象初始化時候,getter就是mountComponent時候傳入updateComponent方法薛窥。
11胖烛、執(zhí)行updateComponent方法,會調(diào)用組件_update方法诅迷,傳入當前組件_render函數(shù)返回值佩番。
12、在_render函數(shù)中罢杉,獲取當前組件data中的值(當前最新的值)趟畏。
13、在_update中滩租,調(diào)用patch方法赋秀,傳入新舊Vnode利朵。
14、在patch中沃琅,調(diào)用patchVnode方法哗咆,開始diff比較虛擬dom樹。