zoukankan      html  css  js  c++  java
  • vue中$watch源码阅读笔记

     项目中使用了vue,一直在比较computed和$watch的使用场景,今天周末抽时间看了下vue中$watch的源码部分,也查阅了一些别人的文章,暂时把自己的笔记记录于此,供以后查阅:

    实现一个简单的$watch:

    const v = new Vue({
    data:{
      a: 1,
      b: {
        c: 3
      }
    }
    })
    // 实例方法$watch,监听属性"a"
    v.$watch("a",()=>console.log("你修改了a"))
                //当Vue实例上的a变化时$watch的回调
    setTimeout(()=>{
    v.a = 2
    // 设置定时器,修改a
    },1000)

    这个过程大概分为三部分:实例化Vue、调用$watch方法、属性变化,触发回调

    一、实例化Vue:面向对象的编程

    class Vue { //Vue对象
        constructor (options) {
          this.$options=options;
          let data = this._data=this.$options.data;
          Object.keys(data).forEach(key=>this._proxy(key));
          // 拿到data之后,我们循环data里的所有属性,都传入代理函数中
          observe(data,this);
        }
        $watch(expOrFn, cb, options){  //监听赋值方法
          new Watcher(this, expOrFn, cb);
          // 传入的是Vue对象
        }
    
        _proxy(key) { //代理赋值方法
          // 当未开启监听的时候,属性的赋值使用的是代理赋值的方法
          // 而其主要的作用,是当我们访问Vue.a的时候,也就是Vue实例的属性时,我们返回的是Vue.data.a的属性而不是Vue实例上的属性
          var self = this
          Object.defineProperty(self, key, {
            configurable: true,
            enumerable: true,
            get: function proxyGetter () {
              return self._data[key]
              // 返回 Vue实例上data的对应属性值
            },
            set: function proxySetter (val) {
              self._data[key] = val
            }
          })
        }
      }

    注意这里的Object.defineProperty ( obj, key , option) 方法

    总共参数有三个,其中option中包括  set(fn), get(fn), enumerable(boolean), configurable(boolean)

    set会在obj的属性被修改的时候触发,而get是在属性被获取的时候触发,(其实属性的每次赋值,每次取值,都是调用了函数);

    constructor :Vue实例的构造函数,传入参数(options)的时候,constructor 就会被调用,让Vue对象和参数data产生关联,让我们可以通过this.a 或者vm.a来访问data属性,建立关联之后,循环data的所有键名,将其传入到_proxy方法

    $watch:实例化Watcher对象

    _proxy:这个方法是一个代理方法,接收一个键名,作用的对象是Vue对象,

    回头来看Object.defineProperty ( obj, key , option) 这个方法

    Object.defineProperty(self, key, {
            configurable: true,
            enumerable: true,
            get: function proxyGetter () {
              return self._data[key]
              // 返回 Vue实例上data的对应属性值
            },
            set: function proxySetter (val) {
              self._data[key] = val
            }
          })

    这个方法的第一个Obj的参数传入的是self,也就是Vue实例本身,而get方法里,return出来的却是self._data[key], _data在上面的方法当中,已经和参数data相等了,所以当我们访问Vue.a的时候,get方法返回给我们的,是Vue._data.a。

    例如:

    var vm = Vue({
       data:{
           a:1, 
           msg:'我是Vue实例'      
         } 
    })
    console.log(vm.msg) //打印 '我是Vue实例'
    // 理论上来说,msg和a,应该是data上的属性,但是却可以通过vm.msg直接拿到

      当我们在new Vue的时候,传进去的data很可能包括子对象,例如在使用Vue.data.a = {a1:1 , a2:2 }的时候,这种情况是十分常见的,但是刚才的_proxy函数只是循环遍历了key,如果我们要给对象的子对象增加set和get方法的时候,最好的方法就是递归;

      方法也很简单,如果有属性值 == object,那么久把他的属性值拿出来,遍历一次,如果还有,继续遍历,代码如下:

    function defineReactive (obj, key, val) {//  类似_proxy方法,循环增加set和get方法,只不过增加了Dep对象和递归的方法
       var dep = new Dep()
        var childOb = observe(val)
        //这里的val已经是第一次传入的对象所包含的属性或者对象,会在observe进行筛选,决定是否继续递归
        Object.defineProperty(obj, key, {//这个defineProperty方法,作用对象是每次递归传入的对象,会在Observer对象中进行分化
       enumerable: true,
       configurable: true,
       get: ()=>{
            if(Dep.target){//这里判断是否开启监听模式(调用watch)
              dep.addSub(Dep.target)//调用了,则增加一个Watcher对象
            }
            return val//没有启用监听,返回正常应该返回val
          },
       set:newVal=> {var value =  val
            if (newVal === value) {//新值和旧值相同的话,return
              return
            }
            val = newVal
            childOb = observe(newVal)
                  //这里增加observe方法的原因是,当我们给属性赋的值也是对象的时候,同样要递归增加set和get方法
            dep.notify()
                  //这个方法是告诉watch,你该行动了
       }
     })
    }
    function observe (value, vm) {//递归控制函数
        if (!value || typeof value !== 'object') {//这里判断是否为对象,如果不是对象,说明不需要继续递归
          return
        }
      return new Observer(value)//递归
    }

    Opserver对象是使用defineReactive方法循环给参数value设置set和get方法,同时顺便调了observe方法做了一个递归判断,看看是否要从Opserver对象开始再来一遍。

    Dep起到连接的作用:

    class Dep {
        constructor() {
         this.subs = []  //Watcher队列数组
        }
        addSub(sub){
          this.subs.push(sub) //增加一个Watcher
        }
       notify(){
          this.subs.forEach(sub=>sub.update()) //触发Watcher身上的update回调(也就是你传进来的回调)
        }
    }
    Dep.target = null //增加一个空的target,用来存放Watcher

    new Watcher:

    class Watcher { // 当使用了$watch 方法之后,不管有没有监听,或者触发监听,都会执行以下方法
       constructor(vm, expOrFn, cb) {
         this.cb = cb  //调用$watch时候传进来的回调
         this.vm = vm
         this.expOrFn = expOrFn //这里的expOrFn是你要监听的属性或方法也就是$watch方法的第一个参数(为了简单起见,我们这里补考录方法,只考虑单个属性的监听)
         this.value = this.get()//调用自己的get方法,并拿到返回值
       }
       update(){  // 还记得Dep.notify方法里循环的update么?
         this.run()
       }
       run(){//这个方法并不是实例化Watcher的时候执行的,而是监听的变量变化的时候才执行的
         const  value = this.get()
         if(value !==this.value){
           this.value = value
           this.cb.call(this.vm)//触发你穿进来的回调函数,call的作用,我就不说了
         }
       }22             get(){ //向Dep.target 赋值为 Watcher
         Dep.target = this  //将Dep身上的target 赋值为Watcher对象
         const value = this.vm._data[this.expOrFn];//这里拿到你要监听的值,在变化之前的数值
         // 声明value,使用this.vm._data进行赋值,并且触发_data[a]的get事件
         Dep.target = null
         return value
       }
     }

      class Watcher在实例化的时候,重点在于get方法,我们来分析一下,get方法首先把Watcher对象赋值给Dep.target,随后又有一个赋值,const value = this.vm._data[this.exOrFn],之前所做的就是修改了Vue对象的data(_data)的所有属性的get和set?,而Vue对象也作为第一个参数,传给了Watcher对象,这个this.vm._data里的所有属性,在取值的时候,都会触发之前defineReactive 方法.

    回过头来再看看get:

    function defineReactive (obj, key, val) {
        /*.......*/
        Object.defineProperty(obj, key, {
      /*.......*/
      get: ()=>{
        if(Dep.target){ //触发这个get事件之前,我们刚刚对Dep.target赋值为Watcher对象
          dep.addSub(Dep.target)//这里会把我们刚赋值的Dep.target(也就是Watcher对象)添加到监听队列里
        }
        return val
      },
      /*.......*/
     }
    }

    在吧Watcher对象放再Dep.subs数组中之后,new Watcher对象所执行的任务就告一段落,此时我们有:

      1.Dep.subs数组中,已经添加了一个Watcher对象,

      2.Dep对象身上有notify方法,来触发subs队列中的Watcher的update方法,

      3.Watcher对象身上有update方法可以调用run方法可以触发最终我们传进去的回调

    那么如何触发Dep.notify方法,来层层回调,找到Watcher的run呢?

    set:newVal=> {                 
     var value =  val
     if (newVal === value) {
       return
     }
     val = newVal
     childOb = observe(newVal)
     dep.notify()//触发Dep.subs中所有Watcher.update方法
    }

    这里形成了一个回路,当修改了所监听的那个值的时候,这个set方法被触发。

  • 相关阅读:
    SWT 重启案例分析(二)
    SWT 重启案例分析(一)
    手机重启问题 Log 抓取方法
    手机重启问题快速分析定位指南
    SWT 手机重启问题分析指南
    属性动画 使用详解
    补间动画 使用详解
    帧动画 使用详解
    Android动画 使用详解
    Linux grep 命令大全
  • 原文地址:https://www.cnblogs.com/jasonwang2y60/p/6754594.html
Copyright © 2011-2022 走看看