vue computed watch

anchors

题外

  • watcher定义后会立即执行一次getter(不是cb) this.value = this.lazy ? undefined : this.get();

  • defineReactive为在模型上的每个属性, 创建dep. 谁get那个属性就把target watcher添加到dep.subs里去.

  • render watcher执行一次get后, 让不再引用的的属性的dep取消订阅自己.

定义computedwatcher

computedwatcher定义流程

  • vm实例上定义_computedWatchers-
  • 创建watcher(传入选项{ lazy: true }), 创建watcher 时不调用get
  • 非组件实例getter将通过defineProperty挂在vm上, 组件的defineComputed, extend构造函数的时候就执行了, computed的get被挂到了vm.prototypes上进行复用
  • computed watcher的get在render vnode的时候通过get属性触发.
    • 先调用watcher.evaluate, 调用定义的computed.xx.getter, 让相关属性的dep添加这个computed watcher
    • 再调用watcher.depend, 让相关属性的dep添加render watcher
    • watcher在内部依赖的属性更新时触发, watcher在调用get后会触发render watcher的的get执行.
  • v2.5.17中的值不变不重新渲染,也只是昙花一现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var computedWatcherOptions = { lazy: true };
function initComputed (vm, computed) {
// $flow-disable-line
var watchers = vm._computedWatchers = Object.create(null);
// computed properties are just getters during SSR
var isSSR = isServerRendering();
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
// new vue上的computed初始化
if (!(key in vm)) {
defineComputed(vm, key, userDef);
}
}
}
1
2
3
4
5
6
7
8
9
10
// 组件的defineComputed, 在vue.extend上调用
if (Sub.options.computed) {
initComputed$1(Sub);
}
function initComputed$1 (Comp) {
var computed = Comp.options.computed;
for (var key in computed) {
defineComputed(Comp.prototype, key, computed[key]);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function defineComputed (
target,
key,
userDef
) {
var shouldCache = !isServerRendering();
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef;
sharedPropertyDefinition.set = noop;
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop;
sharedPropertyDefinition.set = userDef.set
? userDef.set
: noop;
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
function createComputedGetter (key) {
// 返回的这个函数就是vm.computedProperty的getter
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
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get(); // 调用定义的computed.xx.getter,
this.dirty = false;
};
/**
* Depend on all deps collected by this watcher.
*/
Watcher.prototype.depend = function depend () {
var this$1 = this;
var i = this.deps.length;
while (i--) {
this$1.deps[i].depend();
}
};
  • Watcher computed部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var Watcher = function Watcher (
vm,
expOrFn, // user defined getter
cb, // noop in computed watcher
options // { lazy: true }
) {
this.vm = vm;
vm._watchers.push(this);
// options
if (options) {
this.lazy = !!options.lazy;
}
this.cb = cb;
this.id = ++uid$1; // uid for batching
this.active = true;
this.dirty = this.lazy; // for lazy watchers
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: '';
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
}
this.value = this.lazy // 创建watcher 时不调用get
? undefined
: this.get();
};

computedwatcher更新流程

  • setter触发update, 不加入队列, this.dirty = true;
  • render watcher加入队列, render watcher生成vnode时再去调用vm.xx的getter => computed watcher的get
  • 重新收集computed watcher依赖, 更新视图
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
    this.dirty = true;
    } else if (this.sync) {
    this.run();
    } else {
    queueWatcher(this);
    }
    };
    // 调用vm.xx的getter, dirty=true执行evaluate
    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
Watcher.prototype.get = function get () {
pushTarget(this);
value = this.getter.call(vm, vm); // 调用定义的computed.get, 重新收集`computed watcher`的依赖
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
return value
};

userwatcher

watcher创建

watcher可以是数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true; // 用户定义watcher
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) { // 立即执行回调
cb.call(vm, watcher.value);
}
return function unwatchFn () {
watcher.teardown();
}
};

watcher四个options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
if (options) {
this.deep = !!options.deep;
this.user = !!options.user;
this.lazy = !!options.lazy;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb;
this.id = ++uid$1; // uid for batching
this.active = true;
this.dirty = this.lazy; // for lazy watchers
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: '';
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
process.env.NODE_ENV !== 'production' && warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
this.value = this.lazy
? undefined
: this.get();
};
  • deep watch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
} finally {
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
}
const seenObjects = new Set()
function traverse (val) {
_traverse(val, seenObjects);
seenObjects.clear();
}
// 遍历对象, 收集依赖
function _traverse (val, seen) {
var i, keys;
if (val.__ob__) {
var depId = val.__ob__.dep.id;
if (seen.has(depId)) {
return
}
seen.add(depId);
}
if (isA) {
i = val.length;
while (i--) { _traverse(val[i], seen); }
} else {
keys = Object.keys(val);
i = keys.length;
while (i--) { _traverse(val[keys[i]], seen); }
}
}
  • immediate 创建watch后执行一次cb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
cb.call(vm, watcher.value);
}
return function unwatchFn () {
watcher.teardown();
}
};
  • sync watcher setter检测到变化后, 在当前tick中同步执行watcher的回调函数。
1
2
3
4
5
6
7
8
9
10
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};