Watch & Computed
一.Watch实现原理
let vm = new Vue({
el: '#app',
data(){
return {name:'zf'}
},
watch:{
name(newValue,oldValue){
console.log(newValue,oldValue);
}
}
});
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
watch用于监控用户的data变化,数据变化后会触发对应的watch的回调方法
if (opts.watch) {
initWatch(vm,opts.watch);
}
1
2
3
2
3
选项中如果有watch则对watch进行初始化
function initWatch(vm, watch) {
for (const key in watch) {
const handler = watch[key];
// 如果结果值是数组循环创建watcher
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,exprOrFn,handler,options){
// 如果是对象则提取函数 和配置
if(isObject(handler)){
options = handler;
handler = handler.handler;
}
// 如果是字符串就是实例上的函数
if(typeof handler == 'string'){
handler = vm[handler];
}
return vm.$watch(exprOrFn,handler,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这里涉及了watch的三种写法,1.值是对象、2.值是数组、3.值是字符串 (如果是对象可以传入一些watch参数),最终会调用vm.$watch来实现
扩展Vue原型上的方法,都通过mixin的方式来进行添加的。
stateMixin(Vue);
export function stateMixin(Vue) {
Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
options.user = true; // 标记为用户watcher
// 核心就是创建个watcher
const watcher = new Watcher(this, exprOrFn, cb, options);
if(options.immediate){
cb.call(vm,watcher.value)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
class Watcher {
constructor(vm, exprOrFn, callback, options) {
// ...
this.user = !! options.user
if(typeof exprOrFn === 'function'){
this.getter = exprOrFn;
}else{
this.getter = function (){ // 将表达式转换成函数
let path = exprOrFn.split('.');
let obj = vm;
for(let i = 0; i < path.length;i++){
obj = obj[path[i]];
}
return obj;
}
}
this.value = this.get(); // 将初始值记录到value属性上
}
get() {
pushTarget(this); // 把用户定义的watcher存起来
const value = this.getter.call(this.vm); // 执行函数 (依赖收集)
popTarget(); // 移除watcher
return value;
}
run(){
let value = this.get(); // 获取新值
let oldValue = this.value; // 获取老值
this.value = value;
if(this.user){ // 如果是用户watcher 则调用用户传入的callback
this.callback.call(this.vm,value,oldValue)
}
}
}
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
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
还是借助vue响应式原理,默认在取值时将watcher存放到对应属性的dep中,当数据发生变化时通知对应的watcher重新执行
二.Computed实现原理
if (opts.computed) {
initComputed(vm,opts.computed);
}
1
2
3
2
3
function initComputed(vm, computed) {
// 存放计算属性的watcher
const watchers = vm._computedWatchers = {};
for (const key in computed) {
const userDef = computed[key];
// 获取get方法
const getter = typeof userDef === 'function' ? userDef : userDef.get;
// 创建计算属性watcher
watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
defineComputed(vm, key, userDef)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
每个计算属性也都是一个
watcher
,计算属性需要表示lazy:true,这样在初始化watcher时不会立即调用计算属性方法
class Watcher {
constructor(vm, exprOrFn, callback, options) {
this.vm = vm;
this.dirty = this.lazy
// ...
this.value = this.lazy ? undefined : this.get(); // 调用get方法 会让渲染watcher执行
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
默认计算属性需要保存一个dirty属性,用来实现缓存功能
function defineComputed(target, key, userDef) {
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key)
} else {
sharedPropertyDefinition.get = createComputedGetter(userDef.get);
sharedPropertyDefinition.set = userDef.set;
}
// 使用defineProperty定义
Object.defineProperty(target, key, sharedPropertyDefinition)
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
创建缓存getter
function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) { // 如果dirty为true
watcher.evaluate();// 计算出新值,并将dirty 更新为false
}
// 如果依赖的值不发生变化,则返回上次计算的结果
return watcher.value
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
watcher.evaluate
evaluate() {
this.value = this.get()
this.dirty = false
}
1
2
3
4
2
3
4
update() {
if (this.lazy) {
this.dirty = true;
} else {
queueWatcher(this);
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
当依赖的属性变化时,会通知watcher调用update方法,此时我们将dirty置换为true。这样再取值时会重新进行计算。
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) { // 计算属性在模板中使用 则存在Dep.target
watcher.depend()
}
return watcher.value
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
1
2
3
4
5
6
2
3
4
5
6
如果计算属性在模板中使用,就让计算属性中依赖的数据也记录渲染watcher,这样依赖的属性发生变化也可以让视图进行刷新