问:下面代码从store中取值,如果template中没有使用msg, 初始化加载会打印吗?其他页面改变store的值,这里会执行打印吗?
computed: {
msg() {
console.log(this.$store.state.name)
return this.$store.state.name
}
}
答: 不会。
computed触发条件:
1.函数内依赖了vue的属性
2.这些属性发生了改变
3.这些属性被页面引用(触发计算属性的getter,比如打印这个计算属性)。
这三个条件同时满足,才会触发computed中定义的某个函数的回调
从以上场景来思考computed的执行
一、在初始化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。
- 实例化Watcher,
注意两个参数:
(1)第二个参数就是computed中的函数体getter,在什么时机执行呢,后面我们到Watcher中去看
(2)第四个参数传入了lazy: true,将所有的computed watcher存到this._computedWatchers,后面要用的 - 执行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使其变为响应式,并挂在vm上,get函数逻辑在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:返回一个函数给到get
(1)上面初始化中存的this._computedWatchers中如果有这个watcher,并且dirty就执行watcher.evaluate()得到value,并返回value(也就是watcher.value)
(2)记住它:这里有一个Dep.target,执行了depend。后面看到watcher代码我们继续看
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初始化变为了响应式数据且挂在到了this上。
2.要想computed的get被执行,那就需要这个响应式数据被使用。也就是在渲染到视图层时(或者this.msg)会触发get拿到最新的value。
重点:这里要注意一个条件:watcher.dirty 必须要dirty是true才会拿值。watcher.evaluate获取最新值就会执行computed的函数体getter。以下代码我们看看watcher部分源码
五、Watcher 部分
- 这里能看到第二参数expOrFn就是
函数体getter。第四参数options中的lazy默认传进来是true,自然的this.dirty也是true。this.value就是undefined。证明初始化的时候不会执行函数体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还记得吗,上面我们讲到:如果计算属性被使用,进入计算属性的get,watcher.evaluate在dirty为true的时候执行
(1)通过Watcher中的get方法拿值: this.get(),在 this.get()中执行computed的函数体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) 在这里,函数体getter的执行了。执行函数体,就会触发依赖值的get进行计算拿到最新值
// class Watcher {
以下是Watcher中的方法
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 将自身设置为订阅对象
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
}
到这里我们就明白了,初始化的时候传入lazy,lazy = dirty = true。所以渲染到视图层时会触发get会执行取值,然后dirty变为了false。
那么想要再次执行就必须有一个方法去将dirty变为true,我们找一找
- 上面代码有 pushTarget(this),执行
函数体getter后,再 popTarget()
注意这里push之后又pop删掉了,Dep.target会变化
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。原来是一个发布订阅器,可以看到最后执行订阅对象的update方法
原来 pushTarget(this)是把watcher自己当作了订阅对象,给别人订阅,这里watcher就是Dep.target。我们看代码:下面有一个Dep. depend,如果执行就会跑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方法,参数是订阅器Dep。最后执行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);
}
}
}
看着这里你应该明白了:
(1)Watcher是监听器,Vue会提供观察者去订阅他,如果观察者发现需要更新某个操作,会通知Watcher,watcher会执行update进行更新。
(2)computed的watcher参数不一样,传入了一个lazy:true。所以lazy:true具有代表性
我们可以想到:这里的update肯定要将dirty变为了true,不然无法执行我们上面说到的执行函数体getter
// class Watcher {
以下是Watcher中的方法, 只有computed的watcher的lazy默认传入的是true
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
}
现在,只要在适当时机触发Dep. notify() 就能实现update函数执行将dirty更新为true。
思考
(1)谁是观察者来执行Dep. notify() ?自然是依赖的数据变化了,就去notify,将dirty改为true
(2)什么时候执行Dep. depend?想一想应该也是观察者,也就是依赖的数据变化,我们往下一步步看找答案
(3)还记得上面的createComputedGetter吗,代码里面有个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();
}
}
先解决第一个和第二个问题:依赖的数据是响应式的,那initData时候,响应式数据的set中一定有notify的执行。看下面代码验证一下
六、我们回到initState代码
- 初始化data都会走到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:所有挂在的数据都走到了defineReactive$$1,还判断了数据是数组的情况,observeArray是循环再执行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]);
}
}
}
- 继续看defineReactive$$1, 确实在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中可以看到,首先实例化一个发布订阅器 const dep = new Dep()
(2)当响应式数据被触发会判断Dep.target,Dep.target如果有值(上面说过,它就是一个watcher),就执行订阅器的dep.depend()
(3)对第二个问题解决:什么时候执行Dep. depend? 在上面代码中看到了。
class Dep {
depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
}
}
执行watcher的addDep方法,
this.newDeps 存储了该响应式数据的dep实例
实际上就是给第一步实例的dep注入了watcher,以便下面set中notify的时候执行指定的watcher的update方法,这里就建立了对应关系。
// 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)上面第三个问题问到watcher.depend。它做了什么呢?这需要仔细看看
我们捋一捋整个流程的顺序
第一步:进入计算属性的get执行以下代码,也就是createComputedGetter
if (watcher.dirty) {
watcher.evaluate();
}
这里还不慌执行,我们在第四步执行
if (Dep.target) {
watcher.depend();
}
第二步:执行watcher.get() , 并执行函数体getter: this.getter.call(vm, vm)
执行的时候就会进入到依赖的数据的get中,也就是defineReactive$$1 上面执行dep.depend(),我们知道this.newDeps搜集了响应式数据的发布订阅器。依赖多少响应式数据就搜集多少
当前的Dep.target(当前计算属性的watcher)通过this.newDeps搜集了所有的响应式依赖
get () {
pushTarget(this);
let value;
const vm = this.vm;
try {
value = this.getter.call(vm, vm);
} finally {
popTarget();
this.cleanupDeps();
}
return value
}
第三步:执行上面的finally,this.cleanupDeps。this.deps拿到了响应式依赖
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;
}
第四步:执行完watcher.get。终于来到重点环节.现在知道了this.deps的来源了。现在回到了计算属性的get来执行这一段
if (Dep.target) {
watcher.depend();
}
等等,watcher.depend循环执行了所有依赖项的发布订阅器Dep.depend()方法。这是什么意思?this.deps里面已经是所有的依赖了,再执行一遍?
这里有个巧妙的转变:还记得我们上面说的push之后又pop删掉了,Dep.target会变化吧。它变为了谁,我们继续看
通过断点,我发现targetStack里面还有其他东西。 Dep.target变为了 :this.update(),这玩意儿一看就知道是更新用的。

通过全局搜索new Watcher才知道:有三类watcher
(1)computed的watcher。默认lazy并不马上执行get,这个我们是知道的
(2)自定义watcher,也就是我们自己写的this.$watch,马上执行get,这里暂且不论。代码为证:
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 触发渲染视图用的,马上执行get。我们看看简化代码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
}
明白了:初始化的时候mountComponent,执行了new Watcher中的get。在targetStack数组中,第一个元素就是会引发渲染的 updateComponent的watcher。
所以:当计算属性的get执行到下面代码时
Dep.target此时为render watcher。
// 如果页面上没有使用计算属性,
//但是又通过this.computed取值触发计算属性的get
//这里就会是undefined
if (Dep.target) {
watcher.depend();
}
依次执行下面的代码
class Watcher {
depend () {
let i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
}
}
此时Dep.target 就变为了updateComponent的watcher。
所以此时addDep是给render watcher添加依赖
class Dep {
depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
}
}
1个Dep可以被多个Watcher订阅,1个Watcher也可以订阅多个依赖。通过遍历计算属性watcher的deps,让render watcher去订阅他们: 计算属性的值变化了,就会发render watcher 的update执行以触发更新视图。
总结一下整个流程:
初始化的时候lazy = dirty = true。将计算属性设置为响应式并重写了get
当页面上要使用计算属性时,会进入到计算属性的get,get中dirty = true会进入到watcher.get并执行pushTarget(this)。此时全局的Dep.target就是计算属性的watcher
此时执行计算属性的
函数体getter,里面有一个响应式数据,比如:this.$store.state.name.就会进入到该响应式数据的get。这个get就是我们上面说的过程:发现Dep.target(当前watcher)有值,就将此watcher放入了实例的Dep(订阅watcher)在set的时候如果值没变化,直接return。有变化就利用dep. notify通知watcher执行update 把dirty设置为true。这里的响应式数据返回一个新值
响应式数据拿到新值,计算属性也就拿到了新值。然后popTarget。 此时的Dep.target变为了render watcher 。然后执行watcher.depend(),给render watcher 添加依赖。触发变化后,引起试图更新