一、watch的用法和内部原理
用法:
vm.$watch(expOfFn,callback,{options})
options中包括deep,immediate
deep:可以发现对象内部的变化,指定deep:true使用
immediate:立即以当表达式当前值触发回调,指定immediate:true使用
内部原理:
vm.$watch是对Watcher的一种封装,Watcher之前也讲过,就是一个中介,数据发生变化通知Watcher,Watcher调用update执行回调。
主要功能为 const watcher = new Watcher(vm,expOfFn,cb,options)
(1)在new Watcher中,先判断是否使用了imediate参数,使用了则立即执行cb
(2)然后判断是否使用了deep参数,如果是,则使用traverse进行深度观察
(3)判断Watcher是否已经订阅了该Dep,防止重复订阅,如果没有订阅的话,Watcher通过this.deps.push(dep)记录自己被哪些Dep收集了
(4)触发dep.addSub(this)来将自己订阅到Dep中
所以Watcher和Dep是多对多的关系(Dep记录了自己要通知哪些Watcher,Watcher也记录了自己要被哪些Dep通知)
Watcher中expOrFn有个细节:Watcher中的expOrFn是支持函数的(看英文也知道)
//如果是函数,则直接赋值给getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
}
//如果是表达式,则用parsePath解析数据
else {
this.getter = parsePath(expOrFn)
}
当expOfFn是函数时,它不仅可以动态的返回数据,而且函数中所有的响应式数据也会被Watcher观察(exOfFn支持函数其实与Computed的实现原理有很大关系)
例如:
//这里就向watch中传入了函数(也就是expOfFn为函数)
this.$watch(function(){
//这时候watcher就会观察name和age两个数据,其中一个有变化都会得到通知,触发回调
return this.name + this.age
},function(newValue,oldValue){
console.log(newValue,oldValue)
})
二、computed内部原理
computed是定义在vm上一个特殊的getter方法,模板读取计算属性其实就是触发计算属性的getter方法,这个getter方法用于计算当前计算属性的值,然后会用Watcher取观察计算属性中用到的数据的变化,同时将计算属性的Watcher的dirty属性改为false,这样再次读取计算属性时,如果dirty还为false就不再重新计算.
1.计算属性结果会缓存:
计算属性的结果会被缓存,且只有在计算属性所依赖的响应式属性发生变化后才会重新计算,是通过Watcher的dirty属性来分辨的,为true则要重新计算,为false则不要重新计算。
2.计算属性状态变化流程:
(1)当计算属性中状态发生变化时,计算属性的Watcher和组件的Watcher都会得到通知
(2)计算属性的Watcher会将自己的dirty值设为true,当下一次读取计算属性时,会重新计算一次值
(3)组件的Watcher也会得到通知,执行render进行重新渲染,由于重新渲染也会读取到计算属性的值,然后这个时候经过(2)步骤,计算属性Watcher的dirty值已经为true,所以就会重新计算一次计算属性的值,用于本次渲染。
在vue2.5.17版本中又对计算属性的实现做了一个改动
在此之前,计算属性存在着一个问题:
组件Watcher观察计算属性所有依赖数据的变化,如果计算属性中的状态产生了变化,但是计算属性最终的值并没有改变,这时候计算属性也会重新渲染,只不过虚拟DOM的diff中没有变化,视觉上没用变化而已,但是走了渲染流程。
有人就在Vue的GitHub Issues里面提出了这个问题,为了解决这个问题,作者对计算属性的实现做了一些改动:
组件的Watcher不再观察计算属性用到的数据的变化,而是让计算属性的Watcher得到通知后,取计算一次计算属性的值,和之前的值做比较,如果不一样再去主动通知组件Watcher进行渲染操作。组件的Watcher不再观察数据的变化,而只观察计算属性的Watcher,然后计算属性主动去通知组件。发送通知就是执行getAndInvoke函数的回调。
三、watch和computed对比
(1)watch不支持缓存,数据变化执行回调;computed支持缓存,数据变化则重新计算
(2)computed不支持异步,watch支持异步监听
(3)computed依赖的数据和watch监听的数据都是data声明过,或者props中的数据