Vue源码学习(十三):实现watch(一):方法,对象

发布时间 2023-10-28 21:43:15作者: 养肥胖虎

好家伙,

 代码出了点bug,暂时只能实现这两种形式

 

完整代码已开源https://github.com/Fattiger4399/analytic-vue.git

Vue:watch的多种使用方法

watch有非常多种使用方式,我们要对其进行分类讨论处理

 

1.初始化:

//initState.js

if (opts.watch) {
        initWatch(vm);
    }

 

initWatch()方法

function initWatch(vm) {
    //1 获取watch
    let watch = vm.$options.watch
    console.log(watch)
    //2 遍历  { a,b,c}
    for (let key in watch) {
        //2.1获取 他的属性对应的值 (判断)
        let handler = watch[key] //数组 ,对象 ,字符,函数
        if (Array.isArray(handler)) {//数组  []
            handler.forEach(item=>{
                createrWatcher(vm,key,item) 
            })
        } else {//对象 ,字符,函数
           //3创建一个方法来处理
           createrWatcher(vm,key,handler)
        }
    }
}

 

createrWatcher()

//格式化处理
//vm 实例
//exprOrfn key
//hendler key对应的值
//options 自定义配置项 vue自己的为空,用户定义的才有
function createrWatcher(vm,exprOrfn,handler,options){
   //3.1 处理handler
   if(typeof handler ==='object'){
       options = handler; //用户的配置项目
       handler = handler.handler;//这个是一个函数
   }
   if(typeof handler ==='string'){// 'aa'
       handler = vm[handler] //将实例行的方法作为 handler 方法代理和data 一样
   }
   //其他是 函数
   //watch 最终处理 $watch 这个方法
//    console.log(vm,"||vm")
//    console.log(exprOrfn,"||exprOrfn")
//    console.log(handler,"||handler")
//    console.log(options,"||options")

   return vm.$watch(vm,exprOrfn,handler,options)
}

 

原型上挂$watch方法

export function stateMixin(vm) {
    console.log(vm,6666)
    //列队 :1就是vue自己的nextTick  2用户自己的
    vm.prototype.$nextTick = function (cb) { //nextTick: 数据更新之后获取到最新的DOM
        //  console.log(cb)
        nextTick(cb)
    },
    vm.prototype.$watch =function(Vue,exprOrfn,handler,options={}){ //上面格式化处理
        //   console.log(exprOrfn,handler,options)
          //实现watch 方法 就是new  watcher //渲染走 渲染watcher $watch 走 watcher  user false
         //  watch 核心 watcher
         let watcher = new Watcher(Vue,exprOrfn,handler,{...options,user:true})
          
         if(options.immediate){
            handler.call(Vue) //如果有这个immediate 立即执行
         }
    }
    
}

 

 

2.watcher.js

watcher类

class Watcher {
    //vm 实例
    //exprOrfn vm._updata(vm._render()) 
    constructor(vm, exprOrfn, cb, options) {
        // 1.创建类第一步将选项放在实例上
        this.vm = vm;
        this.exprOrfn = exprOrfn;
        this.cb = cb;
        this.options = options;
        // 2. 每一组件只有一个watcher 他是为标识
        this.id = id++
        this.user = !!options.user
        // 3.判断表达式是不是一个函数
        this.deps = []  //watcher 记录有多少dep 依赖
        this.depsId = new Set()
        if (typeof exprOrfn === 'function') {
            this.getter = exprOrfn
        }else{ //{a,b,c}  字符串 变成函数 
            this.getter =function(){ //属性 c.c.c
              let path = exprOrfn.split('.')
              let obj = vm
              for(let i = 0;i<path.length;i++){
                obj  = obj[path[i]]
              }
              return obj //
            }
        }
        // 4.执行渲染页面
        this.value =  this.get() //保存watch 初始值

    }
    addDep(dep) {
        //去重  判断一下 如果dep 相同我们是不用去处理的
        let id = dep.id
        //  console.log(dep.id)
        if (!this.depsId.has(id)) {
            this.deps.push(dep)
            this.depsId.add(id)
            //同时将watcher 放到 dep中
            // console.log(666)
            dep.addSub(this)

        }
        // 现在只需要记住  一个watcher 有多个dep,一个dep 有多个watcher
        //为后面的 component 
    }
    run() { //old new
       let value =  this.get() //new
       let oldValue = this.value //old
       this.value = value
       //执行 hendler (cb) 这个用户wathcer
       if(this.user){
        this.cb.call(this.vm,value,oldValue)
       }
    }
    get() {
        // Dep.target = watcher

        pushTarget(this) //当前的实例添加
      const value = this.getter()// 渲染页面  render()   with(wm){_v(msg,_s(name))} ,取值(执行get这个方法) 走劫持方法
        popTarget(); //删除当前的实例 这两个方法放在 dep 中
        return value
    }
    //问题:要把属性和watcher 绑定在一起   去html页面
    // (1)是不是页面中调用的属性要和watcher 关联起来
    //方法
    //(1)创建一个dep 模块
    updata() { //三次
        //注意:不要数据更新后每次都调用 get 方法 ,get 方法回重新渲染
        //缓存
        // this.get() //重新渲染
        queueWatcher(this)
    }
}

 

3.看看效果

<body>
    <div id="app">{{a}}</div>
    <script src='./dist/vue.js'></script>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                a: 1,
                b:[1,2],
                c:{c:{c:100}}
            },
            methods:{
                aa(){
                    console.log(2000)
                }
            },
            //watch 基本使用方式
            //1 属性 :方法(函数)
            //2 属性 :数组
            //3 属性 :对象
            //4 属性 :字符串
            watch: {
                  'c.c.c'(newValue,oldValue){
                      console.log(newValue,oldValue,'||this isnewValue,oldValue from c.c.c')
                  },
                
                a:{
                    handler(){ //
                        console.log('a数据更新')
                    },
                   immediate:true
                    
                },
                // a:'aa',
            }
        })
        vm.a = 100
        // vm.b[1] = 2
        console.log(vm.c.c.c=2000)
    </script>
</body>