美文网首页
[vue源码03] watch 侦听属性 - 初始化和更新

[vue源码03] watch 侦听属性 - 初始化和更新

作者: woow_wu7 | 来源:发表于2021-09-25 10:00 被阅读0次
    image

    导航

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

    [react] Hooks

    [部署01] Nginx
    [部署02] Docker 部署vue项目
    [部署03] gitlab-CI

    [源码-webpack01-前置知识] AST抽象语法树
    [源码-webpack02-前置知识] Tapable
    [源码-webpack03] 手写webpack - compiler简单编译流程
    [源码] Redux React-Redux01
    [源码] axios
    [源码] vuex
    [源码-vue01] data响应式 和 初始化渲染
    [源码-vue02] computed 响应式 - 初始化,访问,更新过程
    [源码-vue03] watch 侦听属性 - 初始化和更新
    [源码-vue04] Vue.set 和 vm.$set
    [源码-vue05] Vue.extend

    [源码-vue06] Vue.nextTick 和 vm.$nextTick

    前置知识

    一些单词

    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
      }
    }
    

    相关文章

      网友评论

          本文标题:[vue源码03] watch 侦听属性 - 初始化和更新

          本文链接:https://www.haomeiwen.com/subject/tbxxnltx.html