简单的示例
在实际的vue应用中watcher对象与vm实例是分不开的,然而为了简化observe、 dep、watcher的分析过程,特地采用了一个脱离vm的示例进行演示。
import Vue from 'vue'
function test1() {
let observable = Vue.observable;
let Watcher = Vue.Watcher;
let person = {
name: 'haha',
age: 18
}
observable(person)
person._watchers = [];
let watcher = new Watcher(person, 'age', function (val) {
console.log('the age is' , val)
})
console.log(watcher.value)
person.age = 16;
// person.age = 17;
Vue.nextTick(function () {
console.log(watcher.value)
})
}
test1();
该例子中在Vue上额外挂载了watcher对象,以便对外暴露。
1.通过observable将data数据进行get、set的设置
2.通过new Watcher 实现对age数据的监听
3.watcher.update会推入到一个异步队列进行批次更新, 所以在 Vue.nextTick可取到最新的值
observe、dep、watcher关系图
observe.png1.observer递归遍历data数据,为其属性设置get/set
- 在observer的get方法中实现依赖收集, 对使用到该数据的watcher对象进行订阅。
3.在observer的set方法中,数据发生变更则通知其订阅的watcher对象进行update
4.在new Watcher的时候会对其监听的数据进行一次获取即this.getter方法, 通过该方法会进入到observer的get方法中
5.dep.notify通知其订阅的watcher进行更新, 通常情况下会将更新的对象放入一个queue队列采用异步更新。
watcher部分源码
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
// options
...
this.cb = cb;
this.id = ++uid$2; // 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();
};
$get & run
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
...
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get();
...
}
}
};
render Watcher
new Watcher.png1.每个vm实例都会进行一个new watcher, 在该对象的getter上挂载updateComponent方法
- 在get方法中调用updateComponent完成render的vnode以及真实节点的渲染, 在render的过程中template中涉及的渲染数据都会对该watcher对象进行订阅
watcher.cleanupDeps作用
在写Vue模板的时候,模板中涉及的渲染数据都会对render watcher进行订阅。 如果我们更改的数据并不会影响到页面的渲染,安装正常的逻辑是不需要对render watcher进行订阅的。 例如v-if的一些条件判断,在false的情况 下其中的data不需要订阅render watcher, 所以需要对其进行解绑。测试用例如下
<template>
<div>
<p v-if="countFlag"> you counter is {{count}}</p>
<p>another is {{msg}}</p>
<span @click="countInCreate">to create num</span>
<span @click="setCountFlag(false)">hide counter</span>
</div>
</template>
<script>
export default {
data () {
return {
count: 1,
msg: 'test if',
countFlag: true
}
},
methods: {
countInCreate() {
this.count++;
},
setCountFlag(val) {
this.countFlag = false;
}
}
}
</script>
可在Watcher.prototype.get中打断点进行测试, 点击countInCreate方法, 断点可以打入get中,接下来如果将this.countFlag设为false,则watcher.run并不会执行。
array方法重写
在Vue的开发过程中经常会遇到一些关于数组没有自动监听响应的问题, Vue只是对array的一些原生方法进行了重写。有下面代码可知使用push、pop、shift、unshift、splice、sort、reverse可实现自动监听, 爱用concat的小伙伴需谨慎。
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// notify change
ob.dep.notify();
return result
});
});
网友评论