导航

[[深刻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对象中的 keyhandler: 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 watcheractive: 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}}