前置知识点:ExecutionSchedulinghttps://segmentfault.com/a/11...计算属性https://segmentfault.com/a/11...文在上一篇介绍了计算属性的实现原理,这是对监听器的原理实现的介绍,在Vue中经常被拿来和计算属性类比。所谓监听watch,本质上就是观察响应式数据是否发生变化,当数据发生变化时通知并执行相应的回调函数:watch(obj,()=>{console.log('datahaschanged')})//修改数据导致响应式数据变化obj.foo++本质上是使用副作用函数effect和调度选项option.scheduler:effect(()=>{console.log(obj.foo)},//options{scheduler(){//当obj.foo发生变化时,执行scheduler调度函数}})如果副作用函数有scheduler选项,当响应式数据发生变化时,会触发scheduler调度函数的执行,而不是直接触发副作用函数。利用这一点,可以实现最简单的watch函数:functionwatch(source,cb){effect(//触发读取操作,从而建立连接()=>source.foo,{scheduler(){//当obj.foo发生变化时,执行scheduler调度函数cb()}})}但是这个太基础了,只能监听obj.foo的属性变化,所以需要封装一个通用的读操作使得watchuniversal:functionwatch(source,cb){effect(//触发读取操作,从而建立连接//调用函数递归读取并为每条数据建立连接()=>traverse(source),{scheduler(){//当obj.foo变化时,执行scheduler调度函数cb()}})}functiontraverse(value,seen=newSet()){//如果值是原始数据类型,或者已经被读取,什么也不做if(typeofvalue!=='object'||value===null||seen.has(value))return//给seen添加数据,表示已经遍历,避免死循环seen.add(value)//假设value是一个对象,暂时不考虑数组。for(constkinvalue){//递归调用traversetraverse(value[k],seen)}returnvalue}这样就可以读取对象上的任意属性,这样当任意属性发生变化时,可以触发回调函数要执行,watch不仅Observable响应函数还可以接受getter函数:watch(//getterfunction()=>obj.foo,//callbackfunction()=>console.log('obj.foovaluechanged'))在getter函数内部,可以指定手表依赖哪些响应式数据。只有当数据发生变化时,才会触发回调函数执行:functionwatch(source,cn){//定义getterletgetter//如果source是一个函数,说明用户传递的是一个getterif(typeofsource==='function'){getter=source}else{//否则按照原来的调用递归读取gettertraverse=()=>traverse(source)}effect(//执行getter获取值()=>getter(),{scheduler(){//当obj.foo发生变化时,执行scheduler调度函数cb()}})}此时功能已经比较完善,但仍然是一个少了一点,获取不到变化前后的值,但是在Vue.js中是可以的:watch(//getterfunction()=>obj.foo,//callbackfunction(newVal,oldVal)=>console.log(newVal,oldVal))所以需要获取新值和旧值,那么可以使用lazy选项ofeffect:aboutlazy请参考:https://segmentfault.com/a/11...functionwatch(source,cn){//定义getterletgetter//如果source是一个函数,表示用户passedgetterif(typeofsource==='function'){getter=source}else{//否则按照原来的调用递归读取gettertraverse=()=>traverse(source)}letoldValue,newValue//使用effect注册副作用函数时启用lazy选项,并将返回值存储在effectFn中,以供后续手动调用consteffectFn=effect(//执行getter获取值()=>getter(),{lazy:true,scheduler(){//重新执行effectFn得到新值newValue=effectFn()//使用旧值和新值作为回调函数的参数//当obj.foo变化时,执行schedulerschedulingfunctioncb(newValue,oldValue)//更新旧值,否则下次会得到错误的旧值ValueoldValue=newValue}})//手动调用副作用函数获取旧值oldValue=effectFn()}代码底部,手动调用effectFn函数返回的值是旧值,即第一次执行的值,当触发scheduler调度函数时,会再次调用effectFn得到新的价值。
