乐趣区

关于vue.js:vue源码03-watch-侦听属性-初始化和更新

导航

[[深刻 01] 执行上下文 ](https://juejin.im/post/684490…)
[[深刻 02] 原型链 ](https://juejin.im/post/684490…)
[[深刻 03] 继承 ](https://juejin.im/post/684490…)
[[深刻 04] 事件循环 ](https://juejin.im/post/684490…)
[[深刻 05] 柯里化 偏函数 函数记忆 ](https://juejin.im/post/684490…)
[[深刻 06] 隐式转换 和 运算符 ](https://juejin.im/post/684490…)
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…)
[[深刻 08] 前端平安 ](https://juejin.im/post/684490…)
[[深刻 09] 深浅拷贝 ](https://juejin.im/post/684490…)
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…)
[[深刻 11] 前端路由 ](https://juejin.im/post/684490…)
[[深刻 12] 前端模块化 ](https://juejin.im/post/684490…)
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定 ](https://juejin.im/post/684490…)
[[深刻 14] canvas](https://juejin.im/post/684490…)
[[深刻 15] webSocket](https://juejin.im/post/684490…)
[[深刻 16] webpack](https://juejin.im/post/684490…)
[[深刻 17] http 和 https](https://juejin.im/post/684490…)
[[深刻 18] CSS-interview](https://juejin.im/post/684490…)
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…)
[[深刻 20] 手写函数 ](https://juejin.im/post/684490…)

[[react] Hooks](https://juejin.im/post/684490…)

[[部署 01] Nginx](https://juejin.im/post/684490…)
[[部署 02] Docker 部署 vue 我的项目 ](https://juejin.im/post/684490…)
[[部署 03] gitlab-CI](https://juejin.im/post/684490…)

[[源码 -webpack01- 前置常识] AST 形象语法树 ](https://juejin.im/post/684490…)
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…)
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程 ](https://juejin.im/post/684490…)
[[源码] Redux React-Redux01](https://juejin.im/post/684490…)
[[源码] axios ](https://juejin.im/post/684490…)
[[源码] vuex ](https://juejin.im/post/684490…)
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…)
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…)
[[源码 -vue03] watch 侦听属性 – 初始化和更新 ](https://juejin.im/post/684490…)
[[源码 -vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490…)
[[源码 -vue05] Vue.extend ](https://juejin.im/post/684490…)

[[源码 -vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790…)

前置常识

一些单词

somewhat:有点
(somewhat expensive operation 操作有点低廉)

teardown:卸载 

应用案例

<template>
  <div class="about">
    <h1>This is a watch page</h1>

    <div>count = {{count}}</div>
    <button @click="changeCount">change count</button>
    <br />
    <br />

    <div>immediate 立刻执行:count1 = {{count1}}</div>
    <button @click="changeCount1">change count1</button>
    <br />
    <br />

    <div>deep 深度观测 - 遇到问题是新旧值一样,能够用 commputed 做深拷贝:count2 = {{nestObj.count2}}</div>
    <button @click="changeCount2">change count2</button>
    <br />
    <br />

    <div>count3 = {{nestObj.count3}}</div>
    <button @click="changeCount3">change count3</button>
    <br />
    <br />

    <button @click="changeTestArr">change testArr</button>
    <br />
    <br />

    <button @click="changeAll"> 扭转所有数据 - 验证 sync</button>
  </div>
</template>


<script>
export default {data() {
    return {
      count: 0,
      count1: 1,
      nestObj: {
        count2: 2,
        count3: 3
      },
      testArr: {
        count4: 4,
        count5: 5
      },
      testHandlerIsFunctionName: 6,
    };
  },
  computed: {deepCopyNestObj() {return JSON.parse(JSON.stringify(this.nestObj))
    }
  },
  watch: {count: function(val, newVal) { // ---------------------------------- 函数
      console.log(val, newVal);
    },
    count1: {handler(v, oldv) {console.log(v, oldv, "immediate 立刻执行,不须要依赖变动", "后执行");
      },
      immediate: true
    },
    nestObj: { // ------------------------------------------------------ 对象
      handler(v, oldv) {console.log(v.count2, oldv.count2, "sync 再 nextTick 之前先执行");
      },
      deep: true,
      sync: true // 同步 先于 异步的 watch 执行,默认是异步
    },
    deepCopyNestObj(newVal, oldVal) {console.log(newVal.count2, oldVal.count2, 'deep 深度观测 - 遇到问题是新旧值一样,能够用 commputed 做深拷贝')
    },
    "nestObj.count3": function() {
      // 监听对象中的某个属性,能够应用 obj.xxx 的字符串模式作为 key
      console.log("watch 到了 nestObj.count3");
    },
    testArr: [ // ------------------------------------------------------ 数组
      function handler1() {console.log(1111);
      },
      function handler2() {console.log(2222);
      }
    ],
    testHandlerIsFunctionName: 'watchHandlerIsFnName' // --------------- 字符串
    // watchHandlerIsFnName 是一个办法,在 methods 定义的办法
    // 当 testHandlerIsFunctionName 变动,就会调用 watchHandlerIsFnName 办法
  },
  methods: {watchHandlerIsFnName(v, oldV) {console.log(v, oldV, 'watch 对象的 handler 是一个字符串,即一个办法名')
    },
    changeCount() {this.count = this.count + 1;},
    changeCount1() {this.count1 = this.count1 + 1;},
    changeCount2() {this.nestObj.count2 = this.nestObj.count2 + 1;},
    changeCount3() {this.nestObj.count3 = this.nestObj.count3 + 1;},
    changeAll() {
      this.count = this.count + 1;
      this.count1 = this.count1 + 1;
      this.nestObj.count2 = this.nestObj.count2 + 1;
      this.nestObj.count3 = this.nestObj.count3 + 1;
      this.testArr = this.testArr + 1;
      this.testHandlerIsFunctionName = this.testHandlerIsFunctionName + 1
    },
    changeTestArr() {this.testArr = this.testArr + 1}
  }
};
</script>

学习指标

  • watch 的两种用法

    • 通过组件的参数,watch 作为对象
    • 通过 vm.$watch() 办法来调用
  • 防止死循环

    • 比方 watch: {count: {this.count = this.count + 1}}
    • 下面会观测 count 的变动,变动后批改 count,count 变动又持续调用 cb 去批改 count,死循环了
  • wath 对象的 key 对象的 value 的类型

    • function
    • object
    • array
    • string
    • 最终都会把不同类型的 handler 转换成函数
  • watch 对象的 options 对象反对的属性

    • deep

      • 深度监听
      • 循环 (拜访) watch 对象中的 key 对应的 vm.key 嵌套对象的每一个属性,从而触发依赖数据的响应式 get,通过 dep.depend()

        • 向 user watcher 的 newDeps 中增加 dep
        • 向 dep 的 subs 中增加 user watcher
    • immediate

      • 立刻执行 cb,即 wache 对象中的 handler 函数,无需等到依赖变动才去执行
      • 间接调用 cb(watcher.value)
    • sync

      • 保障 (同步 wath 对象的 handler) 在 (一般的 watch 对象的 handler) 后面执行
      • sync 就间接调用 watcher.run() => this.cb.call(this.vm, value, oldValue) 从而间接执行 cb 函数
  • watch 初始化的流程

    1. 解决 watche 对象 key 对应的 value 的各种类型,把 object,array,string 都解决成对象的 function
    2. 执行 vm.$watchg
    3. new userWatcher()

      • constructor 中通过 this.get() 调用 getter 函数,把 watch 对象中的 key 通过 this.getter = parsePath(expOrFn) 办法宰割成数组,通过 vm[key] 去拜访,返回 watch 对象中 key 对应的响应式数据
      • 拜访的时候,又会触发响应式数据的 get 办法,从而进行依赖收集,在 dep 中收集 user watcher,用于更新
  • 更新流程

    • 依赖变动,触发 dep.notify(),玄幻 dep.subs 数据中的 watcher.update() 去更新

      • 如果 sync=true 就间接调用 watcher.run => this.cb.call(this.vm, value, oldValue)
      • 如果 sync=false, queueWatcher => nextTick(flushSchedulerQueue) => watcher.run() => this.cb.call(this.vm, value, oldValue)

watch 源码

  • Vue.prototype._init => initState => initWatch(vm, opts.watch) => createWatcher(vm, key, handler) => vm.$watch(expOrFn, handler, options)
  • initWatch – src/core/instance/state.js

    function initWatch (vm: Component, watch: Object) {// initWatch(vm, opts.watch)
    
    for (const key in watch) {const handler = watch[key]
      // handler
        // watch 对象中的 key 对应的 value
        // 可能是 函数,数组,对象,字符串 (办法名)
    
      if (Array.isArray(handler)) {
        // handler 是数组,就遍历,把每一个成员传入 createWatcher
          // 成员个别是函数
          // 比方
          // watch: {
          //   testArr: [//     function handler1() {//       console.log(1111);
          //     },
          //     function handler2() {//       console.log(2222);
          //     }
          //   ]
          // }
        for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i])
        }
      } else {
        // handler 是对象,函数,字符串
        // 比方
        //  watch: {//   count: function(val, newVal) {//     console.log(val, newVal);
        //   },
        //   count1: {//     handler(v, oldv) {//       console.log(v, oldv, "immediate 立刻执行,不须要依赖变动", "后执行");
        //     },
        //     immediate: true,
        //     deep: true,
        //     sync: true,
        //   },
        //   testHandlerIsFunctionName: 'watchHandlerIsFnName'
        // }
        createWatcher(vm, key, handler)
      }
    }
    }
  • createWatcher – src/core/instance/state.js

    function createWatcher (
    vm: Component,
    expOrFn: string | Function, // watch 对象中的 key
    handler: any, // watch 对象中的 key 对应的 value => 对象,函数,数组成员,字符串
    options?: Object // 初始化时是 undefined
    ) {if (isPlainObject(handler)) {
      // handler 是一个对象
        // 比方
          // count1: {//   handler(v, oldv) {//     console.log(v, oldv);
          //   },
          //   immediate: true,//   deep: true,
          //   sync: true
          // }
      options = handler
      handler = handler.handler
      // handler 是对象,就把 handler 赋值给 options,把 handler 对象的 handler 办法赋值给 handler 变量
      // 其实就是解决参数
    }
    if (typeof handler === 'string') {handler = vm[handler]
      // handler 是一个字符串,就赋值这个字符串代表的办法,在 methods 对象中定义的办法
    }
    
    return vm.$watch(expOrFn, handler, options)
    // 传入 vm.$watch 的 handler 都曾经解决成了 函数
    }
  • Vue.prototype.$watch – src/core/instance/state.js

    Vue.prototype.$watch = function (
      expOrFn: string | Function, 
      // expOrFn
        // watch 对象的 key
      cb: any, 
      // cb 
        // cb 是 watcher 对象中 key 对应的 value 各种状况转换之后的 handler 函数 (可能值是函数,数组,对象,字符串,到这里都转成了函数)
        // 如果不是通过 watch 对象传入 new Vue() 的形式,而是间接通过 vm.$watch 传入,则 cb 就还可能是 ( 函数,对象,数组,字符串)
      options?: Object
      // options 是配置对象
        // options 的属性可能是上面几个
        // handler immediate deep sync 等
    ): Function {
      const vm: Component = this
      if (isPlainObject(cb)) {
        // 这里又判断了 cb 是不是对象,起因如下
          // 1. 因为 $watch 能够通过 vm.$watch 的形式调用
          // 2. 如果是通过传入 new Vue({}) 以 watch 对象的办法,cd 就是曾经是通过解决过后的 函数了,不必再判断对象的状况
        
          return createWatcher(vm, expOrFn, cb, options)
          // 所以如果是下面 1 的状况,就又会调用 createWatcher()去解决 handler 的类型,解决成函数
      }
      options = options || {}
      options.user = true
      // 向 options 对象增加 user 属性,值为 true
    
      const watcher = new Watcher(vm, expOrFn, cb, options)
      // new 一个 user watcher
    
      if (options.immediate) {
        // immediate 属性存在,就立刻执行 cb,即 handler 函数
        try {cb.call(vm, watcher.value)
          // cb 即 handler 函数,是接管两个参数的,这里只传了第一个参数,所以许可的话第二个参数是 undefined
            // 第一个参数 newValue
            // 第二个参数 oldValue
              // watch: {//   count: function(val, newVal) {//     console.log(val, newVal);
              //   }
              // }
        } catch (error) {handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
        }
      }
    
      return function unwatchFn () {
        //  Vue.prototype.$watch 函数,会返回 unwatchFn 函数
    
        watcher.teardown()
        // watcher.teardown()
          // 1. 删除_watchers 中的 user watcher
          // 2. 删除 user watcher 中的 deps 中的所有 dep
    
        // teardown () {//   if (this.active) {
        //     // this.active = true 默认为 true
        //     if (!this.vm._isBeingDestroyed) {//       remove(this.vm._watchers, this)
        //       // 移除 watchers 数组中的 watcher
        //     }
        //     let i = this.deps.length
        //     while (i--) {//       this.deps[i].removeSub(this)
        //       // 同时删除 watcher 的 deps 中的所有 watcher
        //         // 比方 在 user watcher,$watch 办法最初就会删除 user watcher 的 deps 中订阅的 dep
        //     }
        //     this.active = false
        //     // this.active = false
        //   }
        // }
      }
    }
  • watcher – scr/core/observer/watcher.js

    
    export default class Watcher {
    vm: Component;
    expression: string;
    cb: Function; // 比方 user watcher 中的 handler 函数
    id: number;
    deep: boolean;
    user: boolean;
    lazy: boolean; // computed watcher 的标记
    sync: boolean; // user watcher 的 options 对象中的 sync 属性
    dirty: boolean; // 用于 computed watcher
    active: boolean;
    deps: Array<Dep>;
    newDeps: Array<Dep>;
    depIds: SimpleSet;
    newDepIds: SimpleSet;
    before: ?Function;
    getter: Function;
    value: any;
    
    constructor (
      vm: Component,
      expOrFn: string | Function,
      cb: Function,
      options?: ?Object,
      isRenderWatcher?: boolean
    ) {
      this.vm = vm
      if (isRenderWatcher) {vm._watcher = this}
      vm._watchers.push(this)
      // options
      if (options) {
        this.deep = !!options.deep
        this.user = !!options.user // user watcher 的 options.user 默认为 true
        this.lazy = !!options.lazy // computed watcher 的 options.lazy 默认为 true
        this.sync = !!options.sync // 用于 user watcher
        this.before = options.before
      } else {this.deep = this.user = this.lazy = this.sync = false}
      this.cb = cb
      this.id = ++uid // uid for batching
      this.active = true
      this.dirty = this.lazy // for lazy watchers
      this.deps = []
      this.newDeps = []
      this.depIds = new Set()
      this.newDepIds = new Set()
      this.expression = process.env.NODE_ENV !== 'production'
        ? expOrFn.toString()
        : ''
      // parse expression for getter
      if (typeof expOrFn === 'function') {this.getter = expOrFn} else {
        // expOrFn 不是一个函数
          // 因为:user watcher 中的 expOrFn 就是 watch 对象中的 key, 就是一个字符串
          // 所以:用 parsePath 函数就行操作
    
        this.getter = parsePath(expOrFn)
        // this.getter
          // 1. parsePath(expOrFn) 
            // 返回一个函数,返回函数的参数是 vm 实例
              // return function (obj) { 
              //   // 1. path => 比方 expOrFn = path = 'a.b'
              //   // 2. ojb => vm
              //   // 下面 1 和 2,那么上面的循环://     // vm.a  => 拜访到了 a
              //     // vm.a.b => 拜访到了 b
              //   for (let i = 0; i < segments.length; i++) {//     if (!obj) return
              //     obj = obj[segments[i]]
              //   }
              //   return obj
              // }
          // 2. this.getter 是在 watcher.get() 中调用的
            // this.getter.call(vm, vm)
            // 所以:1 中返回函数的参数是 vm
        
    
        // export function parsePath (path: string): any {//   if (bailRE.test(path)) {
        //     return
        //   }
        //   const segments = path.split('.')
        //   // segments 可能状况
        //     // 1.'a.b' 即观测 a 对象的 b 属性 => [a, b]
        //     // 2. a => [a]
        //   return function (obj) { 
        //     // 1. path => 比方 expOrFn = path = 'a.b'
        //     // 2. ojb => vm
        //     // 下面 1 和 2,那么上面的循环://       // vm.a  => 拜访到了 a
        //       // vm.a.b => 拜访到了 b
        //     for (let i = 0; i < segments.length; i++) {//       if (!obj) return
        //       obj = obj[segments[i]]
        //     }
        //     return obj
        //     // 返回 响应式 get 函数中返回的值
        //   }
        // }
       
        
        if (!this.getter) {
          this.getter = noop
          process.env.NODE_ENV !== 'production' && warn(`Failed watching path: "${expOrFn}" ` +
            'Watcher only accepts simple dot-delimited paths.' +
            'For full control, use a function instead.',
            vm
          )
        }
      }
      this.value = this.lazy
        ? undefined
        : this.get()}
    
    /**
     * Evaluate the getter, and re-collect dependencies.
     */
    get () {pushTarget(this)
      let value
      const vm = this.vm
      try {value = this.getter.call(vm, vm)
      } catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`)
        } else {throw e}
      } finally {
        // "touch" every property so they are all tracked as
        // dependencies for deep watching
        if (this.deep) {traverse(value)
        }
        popTarget()
        this.cleanupDeps()}
      return value
    }
    
    /**
     * Add a dependency to this directive.
     */
    addDep (dep: Dep) {
      const id = dep.id
      if (!this.newDepIds.has(id)) {this.newDepIds.add(id)
        this.newDeps.push(dep)
        if (!this.depIds.has(id)) {dep.addSub(this)
        }
      }
    }
    
    /**
     * Clean up for dependency collection.
     */
    cleanupDeps () {
      let i = this.deps.length
      while (i--) {const dep = this.deps[i]
        if (!this.newDepIds.has(dep.id)) {dep.removeSub(this)
        }
      }
      let tmp = this.depIds
      this.depIds = this.newDepIds
      this.newDepIds = tmp
      this.newDepIds.clear()
      tmp = this.deps
      this.deps = this.newDeps
      this.newDeps = tmp
      this.newDeps.length = 0
    }
    
    /**
     * Subscriber interface.
     * Will be called when a dependency changes.
     */
    update () {
      /* istanbul ignore else */
      if (this.lazy) {this.dirty = true} else if (this.sync) {
        // 比方 user watcher 的 options 中配置了 sync:true 时,调用 run 办法
        this.run()} else {queueWatcher(this)
        // export function queueWatcher (watcher: Watcher) {
        //   const id = watcher.id
        //   if (has[id] == null) {//     has[id] = true
        //     if (!flushing) {//       queue.push(watcher)
        //     } else {
        //       // if already flushing, splice the watcher based on its id
        //       // if already past its id, it will be run next immediately.
        //       let i = queue.length - 1
        //       while (i > index && queue[i].id > watcher.id) {
        //         i--
        //       }
        //       queue.splice(i + 1, 0, watcher)
        //     }
        //     // queue the flush
        //     if (!waiting) {
        //       waiting = true
        
        //       if (process.env.NODE_ENV !== 'production' && !config.async) {//         flushSchedulerQueue()
        //         return
        //       }
        //       nextTick(flushSchedulerQueue)
        //     }
        //   }
        // }
      }
    }
    
    /**
     * Scheduler job interface.
     * Will be called by the scheduler.
     */
    run () {if (this.active) {const value = this.get()
        if (
          value !== this.value ||
          // Deep watchers and watchers on Object/Arrays should fire even
          // when the value is the same, because the value may
          // have mutated.
          isObject(value) ||
          this.deep
        ) {
          // set new value
          const oldValue = this.value
          this.value = value
          if (this.user) {
            // 如果是 user watcher 
            try {this.cb.call(this.vm, value, oldValue)
            } catch (e) {handleError(e, this.vm, `callback for watcher "${this.expression}"`)
            }
          } else {this.cb.call(this.vm, value, oldValue)
          }
        }
      }
    }
    
    /**
     * Evaluate the value of the watcher.
     * This only gets called for lazy watchers.
     */
    evaluate () {this.value = this.get()
      this.dirty = false
    }
    
    /**
     * Depend on all deps collected by this watcher.
     */
    depend () {
      let i = this.deps.length
      while (i--) {this.deps[i].depend()}
    }
    
    /**
     * Remove self from all dependencies' subscriber list.
     */
    teardown () {if (this.active) {
        // this.active = true 默认为 true
    
        // remove self from vm's watcher list
        // this is a somewhat expensive operation so we skip it
        // if the vm is being destroyed.
        if (!this.vm._isBeingDestroyed) {remove(this.vm._watchers, this)
          // 移除 watchers 数组中的 watcher
        }
        let i = this.deps.length
        while (i--) {this.deps[i].removeSub(this)
          // 同时删除 watcher 的 deps 中的所有 watcher
            // 比方 在 user watcher,$watch 办法最初就会删除 user watcher 的 deps 中订阅的 dep
        }
        this.active = false
        // this.active = false
      }
    }
    }
    
  • parsePath – src/core/util/lang.js

    export function parsePath (path: string): any {if (bailRE.test(path)) {return}
    const segments = path.split('.')
    // segments 可能状况
      // 1.'a.b' 即观测 a 对象的 b 属性 => [a, b]
      // 2. a => [a]
    
    return function (obj) { 
      // 1
        // 1. path => 比方 expOrFn = path = 'a.b'
        // 2. ojb => vm
        // 下面 1 和 2,那么上面的循环:// vm.a  => 拜访到了 a
          // vm.a.b => 拜访到了 b
      // 2
        // 1. path => 比方 expOrFn = path = 'a'
        // 2. ojb => vm
      for (let i = 0; i < segments.length; i++) {if (!obj) return
        obj = obj[segments[i]]
      }
      return obj
      // 1. 最终会返回 vm.a.b 
      // 2. 最终返回 vm.a
    }
    }
退出移动版