問(wèn):下面代碼從store中取值,如果template中沒(méi)有使用msg, 初始化加載會(huì)打印嗎?其他頁(yè)面改變store的值,這里會(huì)執(zhí)行打印嗎?
computed: {
msg() {
console.log(this.$store.state.name)
return this.$store.state.name
}
}
答: 不會(huì)。
computed觸發(fā)條件:
1.函數(shù)內(nèi)依賴了vue的屬性
2.這些屬性發(fā)生了改變
3.這些屬性被頁(yè)面引用(觸發(fā)計(jì)算屬性的getter,比如打印這個(gè)計(jì)算屬性)。
這三個(gè)條件同時(shí)滿足,才會(huì)觸發(fā)computed中定義的某個(gè)函數(shù)的回調(diào)
從以上場(chǎng)景來(lái)思考computed的執(zhí)行
一、在初始化data后初始化computed,initState源碼段
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.data) { initData(vm);}
if (opts.computed) { initComputed(vm, opts.computed); }
...
}
二、 initComputed初始化computed。
- 實(shí)例化Watcher,
注意兩個(gè)參數(shù):
(1)第二個(gè)參數(shù)就是computed中的函數(shù)體getter,在什么時(shí)機(jī)執(zhí)行呢,后面我們到Watcher中去看
(2)第四個(gè)參數(shù)傳入了lazy: true,將所有的computed watcher存到this._computedWatchers,后面要用的 - 執(zhí)行defineComputed
var computedWatcherOptions = { lazy: true };
function initComputed (vm, computed) {
var watchers = vm._computedWatchers = Object.create(null);
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
if (!(key in vm)) {
defineComputed(vm, key, userDef);
}
}
三、 defineComputed部分代碼:利用Object.defineProperty使其變?yōu)轫憫?yīng)式,并掛在vm上,get函數(shù)邏輯在createComputedGetter
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function defineComputed ( target, key,userDef) {
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = noop;
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
四、 createComputedGetter:返回一個(gè)函數(shù)給到get
(1)上面初始化中存的this._computedWatchers中如果有這個(gè)watcher,并且dirty就執(zhí)行watcher.evaluate()得到value,并返回value(也就是watcher.value)
(2)記住它:這里有一個(gè)Dep.target,執(zhí)行了depend。后面看到watcher代碼我們繼續(xù)看
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
到這我們就能看到:
1.computed初始化變?yōu)榱隧憫?yīng)式數(shù)據(jù)且掛在到了this上。
2.要想computed的get被執(zhí)行,那就需要這個(gè)響應(yīng)式數(shù)據(jù)被使用。也就是在渲染到視圖層時(shí)(或者this.msg)會(huì)觸發(fā)get拿到最新的value。
重點(diǎn):這里要注意一個(gè)條件:watcher.dirty 必須要dirty是true才會(huì)拿值。watcher.evaluate獲取最新值就會(huì)執(zhí)行computed的函數(shù)體getter。以下代碼我們看看watcher部分源碼
五、Watcher 部分
- 這里能看到第二參數(shù)expOrFn就是
函數(shù)體getter。第四參數(shù)options中的lazy默認(rèn)傳進(jìn)來(lái)是true,自然的this.dirty也是true。this.value就是undefined。證明初始化的時(shí)候不會(huì)執(zhí)行函數(shù)體getter
class Watcher {
constructor ( vm, expOrFn, cb,options) {
this.vm = vm;
vm._watchers.push(this);
if (options) {
this.lazy = !!options.lazy;
}
this.dirty = this.lazy; // for lazy watchers
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
}
}
this.value = this.lazy ? undefined : this.get();
}
}
以上是Watcher的constructor,下面我們看看Watcher中的方法
- evaluate還記得嗎,上面我們講到:如果計(jì)算屬性被使用,進(jìn)入計(jì)算屬性的get,watcher.evaluate在dirty為true的時(shí)候執(zhí)行
(1)通過(guò)Watcher中的get方法拿值: this.get(),在 this.get()中執(zhí)行computed的函數(shù)體getter
(2)拿到值后this.dirty變成了false
// class Watcher {
以下是Watcher中的方法
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/。
evaluate () {
this.value = this.get();
this.dirty = false;
}
- 看看get方法
(1) 在這里,函數(shù)體getter的執(zhí)行了。執(zhí)行函數(shù)體,就會(huì)觸發(fā)依賴值的get進(jìn)行計(jì)算拿到最新值
// class Watcher {
以下是Watcher中的方法
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 將自身設(shè)置為訂閱對(duì)象
pushTarget(this);
let value;
const vm = this.vm;
try {
value = this.getter.call(vm, vm);
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
}
到這里我們就明白了,初始化的時(shí)候傳入lazy,lazy = dirty = true。所以渲染到視圖層時(shí)會(huì)觸發(fā)get會(huì)執(zhí)行取值,然后dirty變?yōu)榱薴alse。
那么想要再次執(zhí)行就必須有一個(gè)方法去將dirty變?yōu)閠rue,我們找一找
- 上面代碼有 pushTarget(this),執(zhí)行
函數(shù)體getter后,再 popTarget()
注意這里push之后又pop刪掉了,Dep.target會(huì)變化
Dep.target = null;
const targetStack = [];
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
5.再看看Dep。原來(lái)是一個(gè)發(fā)布訂閱器,可以看到最后執(zhí)行訂閱對(duì)象的update方法
原來(lái) pushTarget(this)是把watcher自己當(dāng)作了訂閱對(duì)象,給別人訂閱,這里watcher就是Dep.target。我們看代碼:下面有一個(gè)Dep. depend,如果執(zhí)行就會(huì)跑watcher上的addDep方法
class Dep {
constructor () {
this.id = uid++;
this.subs = [];
}
addSub (sub) {
this.subs.push(sub);
}
removeSub (sub) {
remove(this.subs, sub);
}
depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice();
if (!config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id);
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}
看看watcher上的addDep方法,參數(shù)是訂閱器Dep。最后執(zhí)行Dep上的addSub將watcher加入到了訂閱器
// class Watcher {
以下是Watcher中的方法
/**
* Add a dependency to this directive.
*/
addDep (dep) {
const id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
}
看著這里你應(yīng)該明白了:
(1)Watcher是監(jiān)聽(tīng)器,Vue會(huì)提供觀察者去訂閱他,如果觀察者發(fā)現(xiàn)需要更新某個(gè)操作,會(huì)通知Watcher,watcher會(huì)執(zhí)行update進(jìn)行更新。
(2)computed的watcher參數(shù)不一樣,傳入了一個(gè)lazy:true。所以lazy:true具有代表性
我們可以想到:這里的update肯定要將dirty變?yōu)榱藅rue,不然無(wú)法執(zhí)行我們上面說(shuō)到的執(zhí)行函數(shù)體getter
// class Watcher {
以下是Watcher中的方法, 只有computed的watcher的lazy默認(rèn)傳入的是true
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
}
現(xiàn)在,只要在適當(dāng)時(shí)機(jī)觸發(fā)Dep. notify() 就能實(shí)現(xiàn)update函數(shù)執(zhí)行將dirty更新為true。
思考
(1)誰(shuí)是觀察者來(lái)執(zhí)行Dep. notify() ?自然是依賴的數(shù)據(jù)變化了,就去notify,將dirty改為true
(2)什么時(shí)候執(zhí)行Dep. depend?想一想應(yīng)該也是觀察者,也就是依賴的數(shù)據(jù)變化,我們往下一步步看找答案
(3)還記得上面的createComputedGetter嗎,代碼里面有個(gè)watcher.depend。它做了什么呢?
computed的get
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
// class Watcher {
以下是Watcher中的方法
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
}
先解決第一個(gè)和第二個(gè)問(wèn)題:依賴的數(shù)據(jù)是響應(yīng)式的,那initData時(shí)候,響應(yīng)式數(shù)據(jù)的set中一定有notify的執(zhí)行??聪旅娲a驗(yàn)證一下
六、我們回到initState代碼
- 初始化data都會(huì)走到observe()
function initState (vm) {
vm._watchers = [];
const opts = vm.$options;
if (opts.data) { initData(vm); } else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) initComputed(vm, opts.computed);
}
function initData (vm) {
// observe data
observe(data, true /* asRootData */);
}
- 再看看Observer:所有掛在的數(shù)據(jù)都走到了defineReactive$$1,還判斷了數(shù)據(jù)是數(shù)組的情況,observeArray是循環(huán)再執(zhí)行observe,這里我們只看普通情況
class Observer {
// number of vms that have this object as root $data
constructor (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
}
}
- 繼續(xù)看defineReactive$$1, 確實(shí)在set里面看到了notify,get里面也有Dep.depend()。
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
const dep = new Dep();
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
let childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val;
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
(1)從get中可以看到,首先實(shí)例化一個(gè)發(fā)布訂閱器 const dep = new Dep()
(2)當(dāng)響應(yīng)式數(shù)據(jù)被觸發(fā)會(huì)判斷Dep.target,Dep.target如果有值(上面說(shuō)過(guò),它就是一個(gè)watcher),就執(zhí)行訂閱器的dep.depend()
(3)對(duì)第二個(gè)問(wèn)題解決:什么時(shí)候執(zhí)行Dep. depend? 在上面代碼中看到了。
class Dep {
depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
}
}
執(zhí)行watcher的addDep方法,
this.newDeps 存儲(chǔ)了該響應(yīng)式數(shù)據(jù)的dep實(shí)例
實(shí)際上就是給第一步實(shí)例的dep注入了watcher,以便下面set中notify的時(shí)候執(zhí)行指定的watcher的update方法,這里就建立了對(duì)應(yīng)關(guān)系。
// class Watcher {
以下是Watcher中的方法
/**
* Add a dependency to this directive.
*/
addDep (dep) {
const id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
}
(4)上面第三個(gè)問(wèn)題問(wèn)到watcher.depend。它做了什么呢?這需要仔細(xì)看看
我們捋一捋整個(gè)流程的順序
第一步:進(jìn)入計(jì)算屬性的get執(zhí)行以下代碼,也就是createComputedGetter
if (watcher.dirty) {
watcher.evaluate();
}
這里還不慌執(zhí)行,我們?cè)诘谒牟綀?zhí)行
if (Dep.target) {
watcher.depend();
}
第二步:執(zhí)行watcher.get() , 并執(zhí)行函數(shù)體getter: this.getter.call(vm, vm)
執(zhí)行的時(shí)候就會(huì)進(jìn)入到依賴的數(shù)據(jù)的get中,也就是defineReactive$$1 上面執(zhí)行dep.depend(),我們知道this.newDeps搜集了響應(yīng)式數(shù)據(jù)的發(fā)布訂閱器。依賴多少響應(yīng)式數(shù)據(jù)就搜集多少
當(dāng)前的Dep.target(當(dāng)前計(jì)算屬性的watcher)通過(guò)this.newDeps搜集了所有的響應(yīng)式依賴
get () {
pushTarget(this);
let value;
const vm = this.vm;
try {
value = this.getter.call(vm, vm);
} finally {
popTarget();
this.cleanupDeps();
}
return value
}
第三步:執(zhí)行上面的finally,this.cleanupDeps。this.deps拿到了響應(yīng)式依賴
cleanupDeps () {
let i = this.deps.length;
while (i--) {
const dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}
let tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
}
第四步:執(zhí)行完watcher.get。終于來(lái)到重點(diǎn)環(huán)節(jié).現(xiàn)在知道了this.deps的來(lái)源了。現(xiàn)在回到了計(jì)算屬性的get來(lái)執(zhí)行這一段
if (Dep.target) {
watcher.depend();
}
等等,watcher.depend循環(huán)執(zhí)行了所有依賴項(xiàng)的發(fā)布訂閱器Dep.depend()方法。這是什么意思?this.deps里面已經(jīng)是所有的依賴了,再執(zhí)行一遍?
這里有個(gè)巧妙的轉(zhuǎn)變:還記得我們上面說(shuō)的push之后又pop刪掉了,Dep.target會(huì)變化吧。它變?yōu)榱苏l(shuí),我們繼續(xù)看
通過(guò)斷點(diǎn),我發(fā)現(xiàn)targetStack里面還有其他東西。 Dep.target變?yōu)榱?:this.update(),這玩意兒一看就知道是更新用的。

通過(guò)全局搜索new Watcher才知道:有三類watcher
(1)computed的watcher。默認(rèn)lazy并不馬上執(zhí)行g(shù)et,這個(gè)我們是知道的
(2)自定義watcher,也就是我們自己寫(xiě)的this.$watch,馬上執(zhí)行g(shù)et,這里暫且不論。代碼為證:
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
const vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
const watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`;
pushTarget();
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);
popTarget();
}
return function unwatchFn () {
watcher.teardown();
}
};
}
(3)render watcher 觸發(fā)渲染視圖用的,馬上執(zhí)行g(shù)et。我們看看簡(jiǎn)化代碼mountComponent
function mountComponent (vm,el,hydrating){
let updateComponent = () => {
vm._update(vm._render(), hydrating);
};
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
return vm
}
明白了:初始化的時(shí)候mountComponent,執(zhí)行了new Watcher中的get。在targetStack數(shù)組中,第一個(gè)元素就是會(huì)引發(fā)渲染的 updateComponent的watcher。
所以:當(dāng)計(jì)算屬性的get執(zhí)行到下面代碼時(shí)
Dep.target此時(shí)為render watcher。
// 如果頁(yè)面上沒(méi)有使用計(jì)算屬性,
//但是又通過(guò)this.computed取值觸發(fā)計(jì)算屬性的get
//這里就會(huì)是undefined
if (Dep.target) {
watcher.depend();
}
依次執(zhí)行下面的代碼
class Watcher {
depend () {
let i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
}
}
此時(shí)Dep.target 就變?yōu)榱藆pdateComponent的watcher。
所以此時(shí)addDep是給render watcher添加依賴
class Dep {
depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
}
}
1個(gè)Dep可以被多個(gè)Watcher訂閱,1個(gè)Watcher也可以訂閱多個(gè)依賴。通過(guò)遍歷計(jì)算屬性watcher的deps,讓render watcher去訂閱他們: 計(jì)算屬性的值變化了,就會(huì)發(fā)render watcher 的update執(zhí)行以觸發(fā)更新視圖。
總結(jié)一下整個(gè)流程:
初始化的時(shí)候lazy = dirty = true。將計(jì)算屬性設(shè)置為響應(yīng)式并重寫(xiě)了get
當(dāng)頁(yè)面上要使用計(jì)算屬性時(shí),會(huì)進(jìn)入到計(jì)算屬性的get,get中dirty = true會(huì)進(jìn)入到watcher.get并執(zhí)行pushTarget(this)。此時(shí)全局的Dep.target就是計(jì)算屬性的watcher
此時(shí)執(zhí)行計(jì)算屬性的
函數(shù)體getter,里面有一個(gè)響應(yīng)式數(shù)據(jù),比如:this.$store.state.name.就會(huì)進(jìn)入到該響應(yīng)式數(shù)據(jù)的get。這個(gè)get就是我們上面說(shuō)的過(guò)程:發(fā)現(xiàn)Dep.target(當(dāng)前watcher)有值,就將此watcher放入了實(shí)例的Dep(訂閱watcher)在set的時(shí)候如果值沒(méi)變化,直接return。有變化就利用dep. notify通知watcher執(zhí)行update 把dirty設(shè)置為true。這里的響應(yīng)式數(shù)據(jù)返回一個(gè)新值
響應(yīng)式數(shù)據(jù)拿到新值,計(jì)算屬性也就拿到了新值。然后popTarget。 此時(shí)的Dep.target變?yōu)榱藃ender watcher 。然后執(zhí)行watcher.depend(),給render watcher 添加依賴。觸發(fā)變化后,引起試圖更新