watch的本质就是观测一个响应式数据,当数据发生变化时通知并执行相应的回调函数,利用了effect和options.scheduler选项,利用副作用函数重新执行时的可调度性,一个watch本身会创建一个effect,当这个effect依赖的响应式数据变化时,会执行该effect的调度函数,即scheduler,这里的scheduler可以认为是“回调”,所以我们只需要在scheduler中执行用户通过watch注册的回调函数即可
比如以下例子:
| 12
 3
 4
 5
 6
 
 | const data={foo:1}const obj = new Proxy(data,{})
 watch(obj,()=>{
 console.log("数据变化了")
 })
 obj.foo++
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 
 | 
 
 function watch(source, cb) {
 let getter;
 
 if (typeof source === "function") {
 getter = source;
 } else {
 
 getter = () => traverse(source);
 }
 
 let oldValue, newValue;
 
 const effectfn = effect(
 
 () => getter,
 {
 lazy: true,
 scheduler() {
 
 newValue = effectfn();
 cb(newValue, oldValue);
 
 oldValue = newValue;
 },
 }
 );
 
 oldValue = effectfn();
 }
 
 function traverse(value, seen = new Set()) {
 
 if (typeof value !== "object" || value === null || seen.has(value)) return;
 
 seen.add(value);
 
 for (let k in value) {
 traverse(value[k], seen);
 }
 }
 
 
 | 
如何拿到新值和旧值:lazy选项创建了一个懒执行的effect,最下面部分我们手动调用effectFn函数得到的返回值就是旧值,即第一次执行得到的值,当变化发生触发scheduler调度函数执行时,会重新调用effectFn函数并得到新值,这样我们总可以拿到旧值和新值,接着把它们传递给回调函数cb即可,再用新值更新旧值
立即执行的watch和回调执行时机:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 
 | function watch(source, cb,options={}) {let getter;
 
 if (typeof source === "function") {
 getter = source;
 } else {
 
 getter = () => traverse(source);
 }
 
 let oldValue, newValue;
 
 const job = () => {
 newValue=effectfn()
 cb(newValue, oldValue);
 
 oldValue = newValue;
 }
 
 
 const effectfn = effect(
 
 () => getter,
 {
 lazy: true,
 scheduler: ()=>{
 
 if(options.flush==='post'){
 const p= Promise.resolve()
 p.then(job)
 }else{
 job()
 }
 }
 }
 );
 if(options.immediate){
 job()
 }else{
 
 oldValue = effectfn();
 }
 }
 
 | 
由于回调函数时立即执行,所以第一次回调执行时没有旧值,因此此时回调函数的oldValue值为undefined
过期的副作用:
watch回调函数接收第三个参数onInvalidate,它是一个函数,类似于事件监听器,我们可以使用onInvalidate函数注册一个回调,这个回调函数会在当前副作用函数过期时执行
使用例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | watch(obj,async(newValue,oldValue,onInvalidate) => {
 
 let expired = false
 
 onInvalidate(()=>{
 
 expired=true
 })
 
 const res=await fetch('path/to/request')
 
 if(!expired){
 finalData=res
 }
 })
 
 obj.foo++;
 setTimeout(()=>{
 
 obj.foo++
 },200)
 
 | 
watch处理过期回调:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 
 | function watch(source, cb,options={}) {let getter;
 
 if (typeof source === "function") {
 getter = source;
 } else {
 
 getter = () => traverse(source);
 }
 
 let oldValue, newValue;
 
 let cleanup
 function onInvalidate(fn){
 cleanup=fn
 }
 
 const job = () => {
 newValue=effectfn()
 
 if(cleanup){
 cleanup()
 }
 
 cb(newValue, oldValue,onInvalidate);
 
 oldValue = newValue;
 }
 
 
 const effectfn = effect(
 
 () => getter,
 {
 lazy: true,
 scheduler: ()=>{
 
 if(options.flush==='post'){
 const p= Promise.resolve()
 p.then(job)
 }else{
 job()
 }
 }
 }
 );
 if(options.immediate){
 job()
 }else{
 
 oldValue = effectfn();
 }
 }
 
 | 
第一次修改obj.foo,立即执行,watch回调函数调用onInvalidata,注册过期回调,接着A请求,加入1000ms返回结果,我们在200ms后第二次修改obj,foo,又会导致watch回调函数执行,会执行过期回调,将expired设为true,则请求A的结果返回将被抛弃,避免过期副作用回调函数带来的影响