Watch原理
当监听状态发生改变就会触发。
使用
// 注意不要使用箭头函数
watch: {
// 第一种方式
watchProps(newVal, oldVal) {
this.xx = "111";
// dosomething
},
// 第二种方式,可以兼容 'watchProps.xx.yy' 以前不懂的时候一直通过 computer 把属性返回出来
'watchProps':{
handler(newVal, oldVal) {
this.xx = "111";
// dosomething
},
immediate: true, // 立即监听
deep:true // 深度监听
}
}
// 第三种方式 ,var vm = new Vue({})
vm.$watch('a.b.c',(newVal,oldVal)=>{
// dosomething
},{
deep:true,
immediate:truem
})
deep,immediate 非必传。
还可以主动取消观察函数 watcher.teardown()
原理
双向数据绑定有一个Watcher类,只是普通的watch实例化没有deep参数只需要加上判断,即可。
还有可以监听函数,将当前函数赋值给getter,监听的函数里面涉及到的状态都会被监听到,发生了变化就会触发watch。
还要新增一个取消观察函数的函数。
watch中的deep:true是如何实现的
当用户指定了 watch 中的deep属性为 true 时,如果当前监控的值是数组或者对象。会对对象中的每一项进行求值,此时会将当前 watcher 存入到对应属性的依赖中,这样数组中对象发生变化时也会通知数据更新
// 初始化Watch
// watch 为用户书写watch
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++) {
// 创建watch,并且把回调函数传递进去
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}
// 创建watch
function createWatcher(
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
// 如果传进来是一个对象
if (isPlainObject(handler)) {
options = handler; // options接受到了
handler = handler.handler;
}
// 如果是字符串 就是可以让我们写“a.b.c”这样的格式
if (typeof handler === "string") {
handler = vm[handler];
}
// 去watch类,走watch方法
return vm.$watch(expOrFn, handler, options);
}
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this;
debugger;
// 这里还留下了一个 用户可以通过 vm.$watch("xx",()....)的调用方法
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options);
}
options = options || {};
options.user = true;
// 在这里才真正去 watcher累里面进行监听
const watcher = new Watcher(vm, expOrFn, cb, options); // 创建watcher,数据更新调用cb
// 如果是immediate为true的话,立即执行传进来的回调函数
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();
};
};
// watch类里面的get方法对deep,和复杂对象处理方法
get () {
pushTarget(this) // 先将当前依赖放到 Dep.target上
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
}
catch (e) {
// 如果是用户定义的watcher则报错给用户看
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) { // 如果需要深度监控
traverse(value) // 会对对象中的每一项取值,取值时会执行对应的get方法
}
popTarget()
}
return value
}
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
// 不是数组也不是对象返回,冰冻对象返回,Vnode实例返回
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode){
return
}
if (val.__ob__) {
const 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)
}
}
Computed
计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值
computed是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而watch是监听已经存在且已挂载到vm上的数据,所以用watch同样可以监听computed计算属性的变化(其它还有data、props)。
原理是基于Watcher类,有一个lazy属性,可以进行缓存作用,如果lazy是true证明是计算属性,直接返回数据,不用继续求值,这就是缓存值的原理。
使用
1.计算属性的getter函数
2.计算属性的setter函数
3.计算属性的cache缓存属性
computed: {
changeMessage: {
// 计算属性:依赖message变化而变化 依赖没变化就不会重新渲染;
get () {
return this.message + 'world'
},
set () {
// 当赋值给计算属性的时候,将调用setter函数。多用于在模板组件中需要修改计算属性自身的值的时候。在mount中修改
}
}
}
// 默认调用的是get
computed: {
reversedMsg() {
// this 指向 vm 实例
return this.msg.split('').reverse().join('')
}
}
原理
默认也是具备缓存的,只要当依赖的属性发生变化时才会更新视图
function initComputed ( vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const 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 // {lazy:true} 初始化lazy求值
)}
// 如果不在vm上
if (!(key in vm)) {
// 将定义新计算的属性在vm实例上
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
// 在正式环境是直接忽略重复定义计算属性,在其他环境的话。
// 如果在data上面定义,在data上面报警告
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
}
// 如果在props上定义,在props上面报警告
else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
export function defineComputed(
target: any,
key: string,
userDef: Object | Function
) {
// 不是服务端渲染则缓存
const 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 (
process.env.NODE_ENV !== "production" &&
sharedPropertyDefinition.set === noop
) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
);
};
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
var computedWatcherOptions = { lazy: true };
function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
// 如果值是脏的 进行求值操作,如果依赖的值没发生变化,就不会重新求值
// 触发watch中的geeter
watcher.evaluate(); // this.firstname lastname
}
if (Dep.target) {
// 让计算属性所依赖的属性 收集渲染watcher
watcher.depend();
}
return watcher.value;
}
};
}
// 调用getter,将dirty改为false
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
// 在这里可以收集依赖,方便以后通知到各个对象
class Dep {
constructor() {
this.subs = [] // subs [watcher]
}
depend() {
this.subs.push(Dep.target)
}
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
Dep.target = null;
observer(obj); // 响应式属性劫持
Watch 和 Computed 应用场景
Computed 计算属性 当属性的依赖有发生变化就会触发。
场景:
1.购物车计算的总金额 多个状态影响一个计算属性(多对一)。
2.模版渲染的状态需要比较复杂的计算。
3.只能是同步代码。
4.多个状态合并为一个状态,提供给Watch监听
Watch 监听器,监听一个状态的变化,只要变化就会触发。
场景:
1.贷款月供 ,当选择还款年限,计算其他的数值并赋值(当状态变化,触发其他数值的变化,一对多)。
2.当组件传值,监听状态做一些相应的操作。
3.既可以是异步代码也可以同步代码。
综上所述:computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据。
各位看官如遇上不理解的地方,或者我文章有不足、错误的地方,欢迎在评论区指出,感谢阅读。