Vue Watch 和 Computed 的区别详解
test
Vue 提供了Watch 和 Computed两种组件级别的属性来供用户监控数据变化,本文将来分析两者的使用和区别。
一:语法区别
// Watch的用法by官方文档
watch: {
a:function(val,oldval){}
b:'someMethod'
c:{
handler:function(val,oldval){},
deep:true
}
d:{
handler:function(val,oldval){},
immediate:true
}
e:[
'handle1',
function handle2 (val,oldval){},
{
handler:function(val,oldval){}
}
],
'e.f':function(val,oldval){}
}
// Computed的用法by官方文档
computed:{
aDouble () {
return this.a * 2
},
aPlus: {
get () {
return this.a + 1
},
set (v) {
this.a = v-1
}
}
}
二:使用场景区别
computed 适合 多个数据变化影响一个数据
watch 适合一个数据的变动影响多个数据或者复杂的运算
三:源码解析
Watch 源码
注意文中中文注释
// 初始化数据的时候,会调用一个initWatch方法来初始化用户定义的Watch
function initWatch (vm, watch) {
for (var key in watch) {
var handler = watch[key];
if (Array.isArray(handler)) {
for (var i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}
// 处理watcher的几种定义方法,将参数统一
function createWatcher (
vm,
expOrFn,
handler,
options
) {
// 处理对象的watcher的定义方法
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
// 处理watch对应的是一个字符串(函数的名称)从vm中取出function
if (typeof handler === 'string') {
handler = vm[handler];
}
// handle 传入,变成了watch的callback参数,expOrFn 是监控的变量表达式
return vm.$watch(expOrFn, handler, options)
}
// 实际例子上的$watch方法
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
// 如果cb还是对象,继续进行调用上面的方法,向下层继续寻找handle
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
// 创建Watcher,调用下面的watcher构造函数
var watcher = new Watcher(vm, expOrFn, cb, options);
// 如果watcher属性里定义了immediate,会直接执行watcher的callback也就是handle方法
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
}
}
return function unwatchFn () {
watcher.teardown();
}
};
}
Watcher的构造函数
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
// options
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$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 = expOrFn.toString();
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
// parsePath 需要处理watcher的表达式,有的时候表达式不一定是vm下的变量名,可能是一个嵌套的变量名,比如a.b.c
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
// 调用get的时候, 会把此watcher加入到响应数据的观察者列表里。
// 这里如果不清楚,移步到 [响应式原理](https://blog.csdn.net/weixin_41275295/article/details/100626832)
this.value = this.lazy
? undefined
: this.get();
};
Watcher.prototype.get = function get () {
// 将该watcher加入到全局变量里,方便get的时候读取到该观察者
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw 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
};
总结下Watch的工作流程
1.初始化组件上配置的watcher属性
2.对watcher属性可能的写法进行规整,得出key和handle
3.通过new Watcher 来创建一个基于key和handle的观察者
4.Watcher 的key为响应式的vm 上的变量,在watcher.get的时候,watcher订阅了对应key的变化。完成响应依赖。
5.当key的值发生了变化,触发watcher的更新方法,并执行回调函数handle
Computed 源码
// 初始化组件的时候,会调用initComputed方法来初始computed属性
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 (getter == null) {
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
}
if (!isSSR) {
// create internal watcher for the computed property.
// computed 最后本质还是创建一个watcher
// watcher的expr就是computed key对象的方法
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.
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else {
// 检测computed里的key是否在data里已经有了
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
}
}
}
}
function defineComputed (
target,
key,
userDef
) {
var shouldCache = !isServerRendering();
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef);
sharedPropertyDefinition.set = noop;
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop;
sharedPropertyDefinition.set = userDef.set || noop;
}
if (sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
// 为computed 的key定义get和set方法
Object.defineProperty(target, key, sharedPropertyDefinition);
}
// 创建computed 属性的get方法
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
// 只有当数据是旧的时候,才会重新调用watcher的get方法,否则直接返回value,这里是有缓存机制的
// 不会每次都去计算
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
// 将computed的watcher下的所有观察者dep加入组件watcher的订阅
// 这样可以保证变量更新的同时通知到computed 和组件一起发生变化
watcher.depend();
}
return watcher.value
}
}
}
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
总结computed的流程
1.初始化的时候会获取computed里的定义。
2.通过遍历第一步的结果,按照computed新的变量名生成Watcher实例。
3.computed的watcher默认是lazy模式的,所以new Watcher 的时候不会调用watcher实例的get方法
4.vue 为computed 里的每个key 代理了一个新的get方法createComputedGetter(),当render页面的时候,新的get调用computed watcher实例的默认get方法。
5.computed执行自定义get方法的时候,会判断是否依赖又变动,没有的话,直接取值,否则去执行获取依赖的变量。
6.获取依赖变量的时候,将computed的watcher实例订阅到依赖的变量的Dep里。
7.走完这一步后,再调用计算列的watcher.depend将组件的watcher实例也订阅到计算列依赖的所有变量的dep中。
function createComputedGetter (key) {
return function computedGetter () {
debugger
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate(); // 5,6
}
if (Dep.target) {
watcher.depend(); // 7
}
return watcher.value // 没有变化直接取值
}
}
}
8.这样,当变量变化后,会通知computed的watcher将dirty设置为true, 以及组件的watcher更新dom。
四:注意事项
1.watcher 初始化是不执行的,如果想初始化就执行的话可以配置immediate属性
2.一般情况不要直接修改computed的值,会报错,一般通过为computed属性自定义set方法,通过改变依赖变量来改变computed的值
3.computed的属性如果不加入在dom中渲染是不会被加入到响应系统的。所以如果只是数据的变动的监控,不映射到dom上,请使用watcher或者其他方法。
4.watcher和computed 属性定义的函数不能使用箭头函数,否则内部this会指向组件的父环境,比如window,导致调用失败。
更多推荐
所有评论(0)