美文网首页
[vue源码02] computed 响应式 - 初始化,访问,

[vue源码02] computed 响应式 - 初始化,访问,

作者: woow_wu7 | 来源:发表于2021-09-25 09:59 被阅读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

    前置知识

    学习目标

    • computed计算属性只有在computed被访问时,才会去计算
      • 因为在new Watcher是computed watcher时,即lazy=true时,在构造函数中没有立即执行get()方法,而是在计算属性被访问时触发computed的响应式get后,执行的get方法中回去调用computed getter函数
    • computed计算属性具有缓存功能
      • 通过dirty=true时,才会去执行watcher.evaluate()方法,才会执行computed wawtcher中的get()即computd 定义的函数,从新计算,计算完后,将this.dirty=false
      • 下次再访问时,会先判断dirty,是false就直接返回缓存的值
    • computed的依赖必须是响应式数据,不然即使依赖变化不会触发computed重新计算
    • 即使computed的依赖变化了,但是computed计算的值没有变化的话,不会从新渲染
    • computed watcher 和 render watcher 和 dep 和 dep.subs 和 watcher.deps 之间复杂的关系
      • computed访问过程
        • 访问computed,触发computed响应式的get函数,get函数中判断如果dirty=true,那么执行computed watchet的evalute方法,即watcher中的get()方法,然后把 Dep.target = computed watcher,执行computed getter函数就是用户指定的computed函数
        • 执行computed getter函数的时,因为有依赖的响应式的data,所以又会触发data的get函数,执行dep.depend(),就是把computed watcher中的 newDeps中添加computed依赖项的 dep,同时向computed依赖项的dep的subs中添加computed watcher,然后又把Dep.target = targetStack数组中的前一个watcher,然后返回计算的结果,并且把 dirty=false
        • 然后判断 Dep.taret 存在,就执行 computed watcher的depend方法,循环遍历computed watcher中的deps,取出dep,执行dep.depend
        • 因为此时的 Dep.target = render watcher,所以dep.depend会向render watcher的 newDeps中添加data的dep,向data的dep中的subs中添加render watcher,那么此时的computed计算属性的依赖项的dep中的subs就是[computed watcher, render watcher]这样就保证了渲染的时候,computed先于render先执行,保证computed有值
      • comuted更新过程
        • 上面的例子中,change的过程,是改变了computed的依赖,之后的一系列流程
        • 触发computed的响应式依赖的dep.notify()方法,循环遍历该依赖的dep.subs数组中的每一个watcher.update()方法,在上面的例子中subs=[computed watcher, render watcher]所以先执行computed watcher
        • compued watcher
          • 在update方法中,会判断lazy=true?,因为computed watcher的lazy=true,所以执行 dirty=true
        • render watcher
          • 在update方法中, lazy不为true, 会执行 queueWatcher(this),就是调用渲染watcher重新求值和渲染

    一些单词

    internal:内部的
    
    Scheduler:调度器
    queue:队列
    ( flushSchedulerQueue : 刷新调度器队列 )
    

    computed源码

    (1) computed的初始化

    • Vue.prototype._init => initState(vm) => initComputed(vm, opts.computed)

    (1-1) initComputed(vm, opts.computed)

    • 主要做了以下事情

      • <font color=blue size=5>(1-1-1) new Watcher(vm, getter, noop, computedWatcherOptions) </font>

        • new了一个 computed watcher
        • 参数
          • computedWatcherOptions
            • 如何知道是一个computed watcher => 主要通过第4个参数computedWatcherOptions => { lazy: true },即comoputed watcher的 lazy属性是true
          • getter
            • 就是用户自己定义的computed对象中的函数
          • noop
            • 是一个空函数
        • 注意点:
          • 在 new Watcher 计算属性watcher的构造函数中
            • this.value=this.lazy ? undefined : this.get()
            • 因为计算属性watcher的lazy=true,所以不会立即去执行 get() 方法
            • <font color=blue>那什么时候去执行呢get()呢?</font>
              • <font color=blue>执行时机就是在template中访问了computed</font>,因为computed又定义了响应式,访问了computed属性就会执行computed的get方法,在get方法中会执行watcher.evaluate()方法,在里面就是去执行 get(),从而去计算computed的结果
      • <font color=blue size=5>(1-1-2) 把computd定义成响应式</font>

        • defineComputed(vm, key, userDef) => Object.defineProperty(target, key, sharedPropertyDefinition) => sharedPropertyDefinition.get => createComputedGetter(key) => computedGetter
        • 也就是说访问computed中的 this.xxxx 就会去执行 computedGetter 函数
      • <font color=blue size=5>(1-1-3) computedGetter - 这个方法很重要 !!!!!!!!!!!!!!!!!!!!!</font>

        • watcher.evaluate() 执行计算属性watcher中的evaluate方法
          • 当dirty=true,并且watcher存在时,就会执行 computed watcher 的 evalute 方法
          • <font color=blue>evalute</font> 方法会执行 <font color=blue>get()</font> 方法,并将 <font color=blue>this.dirty</font> 改为 <font color=blue>false</font>
            • <font color=blue>get()</font>
              • <font color=blue>pushTarget(this)</font>
                • <font color=blue>向 targetStack 数组中 push 一个computed watcher</font>
                • <font color=blue>将 Dep.target 指定为 computed watcher</font>
              • 执行用户在computed对象中定义的方法,即getter方法newName
                • 比如 computed: {newName() {return this.name + 'new' }}
                • 注意:
                  • 在这过程中又会触发 data对象的影响式,即this.name触发响应式data中的get函数,因为访问了data的name属性
                  • data,computed都有自己的响应式
                  • 这里data的响应式又会收集计算属性的watcher,这个放在后面的计算属性的访问流程中去梳理
                    • 主要就是
                      • 向 computed watcher 的newDeps中添加render watcher的dep
                        - 向 render watcher的依赖的属性的dep的 subs 中添加 computed watcher
                        - 详见下文
                        - watcher.depend() 执行计算属性watcher的depend方法
                        - 放在下面访问流程一起分析
    • 源码

    • Vue.prototype._init => initState(vm) => initComputed(vm, opts.computed)

    • initComputed - scr/core/instance/state.js

    initComputed - scr/core/instance/state.js
    ---
    
    function initComputed (vm: Component, computed: Object) {
      const watchers = vm._computedWatchers = Object.create(null)
      // 声明 watchers 和 _computedWatchers 为一个空对象
    
      const isSSR = isServerRendering()
      // 是否是ssr环境
    
      for (const key in computed) {
        const userDef = computed[key]
        // userDef 是 computed 的 getter 函数
    
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        // getter
          // computed getter 可以是函数 或者 具有get方法的对象
          // 这里一般都是函数,并且该函数需要return一个值
    
        if (process.env.NODE_ENV !== 'production' && getter == null) {
          warn(
            `Getter is missing for computed property "${key}".`,
            vm
          )
        }
        // 如果不是函数或者对象就报警告
    
    
        if (!isSSR) {
          // create internal watcher for the computed property.
          // 非ssr环境,即浏览器环境,就新建 computed watcher
          // computed watcher
            // computedWatcherOptions = { lazy: true }
            // getter = 用户自定义的computed对象中的函数
    
          watchers[key] = new Watcher(
            vm,
            getter || noop, // 用户自定义的computed对象中的函数,即 computed getter
            noop,
            computedWatcherOptions, //  { lazy: true }
          )
        }
    
        // component-defined computed properties are already defined on the
        // component prototype. We only need to define computed properties defined
        // at instantiation here.
        // 在 vue.extends 和 new Vue() 过程中都定义了响应式的computed
    
        if (!(key in vm)) {
          defineComputed(vm, key, userDef)
          // defineComputed 将 computed 变成响应式
    
        } else if (process.env.NODE_ENV !== 'production') {
          // 处理重名的情况,在props,data,computed不能用重名的key
          if (key in vm.$data) {
            warn(`The computed property "${key}" is already defined in data.`, vm)
          } else if (vm.$options.props && key in vm.$options.props) {
            warn(`The computed property "${key}" is already defined as a prop.`, vm)
          }
        }
      }
    }
    
    • defineComputed - scr/core/instance/state.js
    defineComputed - scr/core/instance/state.js
    ---
    
    export function defineComputed (
      target: any,
      key: string,
      userDef: Object | Function
    ) {
      const shouldCache = !isServerRendering()
      // shouldCache 如果在浏览器环境就是 true
    
      if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache
          ? createComputedGetter(key) // 定义computed被访问时,触发的get
          : createGetterInvoker(userDef)
        sharedPropertyDefinition.set = noop
      } else {
        // userDef 不是 function,我们直接忽略
        sharedPropertyDefinition.get = userDef.get
          ? shouldCache && userDef.cache !== false
            ? createComputedGetter(key)
            : createGetterInvoker(userDef.get)
          : noop
        sharedPropertyDefinition.set = userDef.set || noop
      }
    
      if (process.env.NODE_ENV !== 'production' &&
          sharedPropertyDefinition.set === noop) {
        sharedPropertyDefinition.set = function () {
          warn(
            `Computed property "${key}" was assigned to but it has no setter.`,
            this
          )
        }
      }
    
      Object.defineProperty(target, key, sharedPropertyDefinition)
      // 定义响应式 computed
        // 1. 当通过 this.xxxx 访问computed,就会触发sharedPropertyDefinition对象中的get
        // 2. get 其实就是下面createComputedGetter返回的 computedGetter函数
    }
    
    • createComputedGetter - scr/core/instance/state.js
    createComputedGetter - scr/core/instance/state.js
    ---
    
    function createComputedGetter (key) {
      return function computedGetter () {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        // 取出每一个 computed watcher
    
        if (watcher) {
          if (watcher.dirty) {
            // watcher.dirty
              // 1. 默认初始化时,comoputed watcher 的 dirty=true
              // 2. 当 dirty=true 就会执行 watcher.evaluate()
              // 3. watcher.evaluate() 执行完后, dirty=false
              // 总结:  dirty=true => watcher.evaluate() => dirty=false
    
            watcher.evaluate()
            // watcher.evaluate()
              // 1. 会去执行 computed watcher 中的 get()
                // pushTarget(this)
                  // 1. 将 computed watcher 添加到  targetStack 数组中
                  // 2. 将 Dep.target = computed watcher
                // 执行 this.getter.call(vm, vm) 即用户自定义的 computed对象中的方法
                  // 1. 列如: computed: {newName() {return this.name + 'new' }}
                  // 2. 因为:computed的newName方法中,依赖了data中的this.name,即访问到了this.name就会触发data响应式的get方法
                  // 3. 所以:ata响应式的get方法执行过程如下
                    // 获取到了this.name的值
                    // 此时,Dep.target 是computed watcher
                    // 然后执行this.name对象的dep类的depend方法进行依赖收集
                      // 向 computed watcher 的newDeps中添加render watcher的dep
                      // 向 render watcher 的 subs 中添加 computed watcher
                //  popTarget()
                  // 1. targetStack.pop()将 computed watcher从targetStack数组中删除
                  // 2. 并且将 Dep.target 指定为数组中的前一个 watcher,没有了就是undefined
              // 2. 将 dirty=false
    
            // evaluate () {
            //   this.value = this.get()
            //   this.dirty = false
            // }
    
            // 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
            // }
    
          }
    
          if (Dep.target) {
            watcher.depend()
    
            // depend () {
            //   let i = this.deps.length
            //   while (i--) {
            //     this.deps[i].depend()
            //   }
            // }
    
            
          }
          return watcher.value
        }
      }
    }
    
    • Watcher - scr/core/observer/watcher.js
    Watcher - scr/core/observer/watcher.js
    ---
    
    export default class Watcher {
      vm: Component;
      expression: string;
      cb: Function;
      id: number;
      deep: boolean;
      user: boolean;
      lazy: boolean;
      sync: boolean;
      dirty: boolean;
      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
          this.lazy = !!options.lazy
          this.sync = !!options.sync
          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 {
          this.getter = parsePath(expOrFn)
          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) {
          this.run()
        } else {
          queueWatcher(this)
        }
      }
    
      /**
       * 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) {
              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) {
          // 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)
          }
          let i = this.deps.length
          while (i--) {
            this.deps[i].removeSub(this)
          }
          this.active = false
        }
      }
    }
    

    (2) computed的访问过程

    • 案例
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <script src="./vue.js"></script>
    </head>
    <body>
      <div id="root">
        <div>{{newName}}</div>
        <button @click="change">change</button>
      </div>
      <script>
        new Vue({
          el: '#root',
          data: {
            name: 'ssssssssssssss'
          },
          computed: {
            newName() {
              return this.name + 'new'
            }
          },
          methods: {
            change() {
              this.name = '222222222222222'
            }
          }
        })
      </script>
    </body>
    </html>
    
    • vm._update(vm._render(), hydrating) 过程中, 当在template模板中使用了 computed 对象中的key的时候,就会触发computed响应式对象的 get 方法
    • computed的响应式get方法就是computedGetter方法,在该方法中,会判断wathcer和watcher.dirty是否存在,存在证明就是computed watcher,就会执行 computed wathcer 的 watcher.evaluate() 方法
    • watcher.evaluate()方法就会执行computed watcher中的get()方法,并将 this.dirty 改为 false
    • watcher.evaluate()
      • get()方法
        • 调用 <font color=red>pushTarget(this)</font>
          • <font color=red>向 targetStack 数组中 push 一个computed watcher</font>
          • <font color=red>将 Dep.target 指定为 computed watcher</font>
        • 然后调用 computed getter 方法,即用户自己定义的computed对象中的方法
          • 比如: computed: {newName() {return this.name + 'new' }}
          • 因为:computed的newName方法中,依赖了data中的this.name,即访问到了this.name就会触发data响应式的get方法
          • 所以:<font color=DarkOrChid>data响应式的get方法执行过程如下:</font>
            • 获取到了this.name的值
            • 此时,Dep.target 是 computed watcher
              • 然后执行this.name对应的dep类的depend方法进行依赖收集
                • <font color=DarkOrChid>向 computed watcher 的newDeps中添加render watcher的对应的data属性的 dep</font>
                • <font color=DarkOrChid>向 render watcher对应的data属性的dep实例的 subs 中添加 computed watcher</font>
                • <font color=DarkOrChid>等于说data的 this.name 和 computed Watcher 具有同一个 dep 实例</font>
              • 执行完上面的步骤后 dep.subs 和 computed watcher.newDeps 的状态是
                • this.name 对应的 dep 实例中的 subs = [computedWatcher]
                • computed watcher 中的 newDeps = [上面的this.name对应的dep]
            • 返回name的值
        • 调用 <font color=red>popTarget()</font>
          • <font color=red>targetStack.pop()将 computed watcher从targetStack数组中删除</font>
          • <font color=red>并且将 Dep.target 指定为数组中的前一个 watcher</font>
            • <font color=red>Dep.target = render watcher</font>
            • 在上面的例子中 targetStack 数组中在执行computed 的getter方法时一共有两个成员
            • 第一个:render watcher
            • 第二个:computed watcher
            • pop后还剩一个render watcher
        • 最后返回computed计算得到的结果值
    • watcher.depend()
      • 当 Dep.target 存在时,才会执行 watcher.depend()
      • 上面执行完,<font color=DarkOrChid>Dep.target = render watcher</font>
      • watcher.depend()
        • 然后执行 <font color=DarkOrChid>compute watcher 中的 watcher.depend() 方法</font>
        • 然后,<font color=DarkOrChid>从后往前,依次取出 computed watcher 中 deps 数组中的 dep,执行 dep.depend()</font>
          • 注意:上面computed watcher 中的 deps 中的 dep,就是this.name对象的dep,里面的subs数组中只有一个computedWatcher
          • 对比:在data对象的属性被访问的时候,也会执行data对应的属性的 dep.depend()
        • <font color=DarkOrChid>Dep.target.addDep(this)</font>
          • <font color=DarkOrChid>此时的 Dep.targt 是 render watcher</font>,因为 popTarget() 操作pop出computed watcher后就只剩render watcher了
          • addDep 前
            • render watcher中的 deps 是空数组
            • render watcher中的 newDeps 是空数组
          • addDep 主要做两件事情
            • <font color=DarkOrChid>向 render watcher 的 newDeps 中添加 该render watcher 对应的datad属性的 dep</font>
            • <font color=DarkOrChid>向 render watcher 对应的data属性对应的dep类的 subs 中添加 render watcher</font>
            • 添加后,data的dep.subs = [computed watcher, render watcher]
              • <font color=red>这样当this.name属性修改后触发对应的set函数,就会触发dep.notify,然后循环sub中的watcher,执行watcher.update()方法</font>
              • <font color=red>[computed watcher, render watcher]这样的顺序保证了在render的时候,computed一定有值</font>

    (3) computed的更新过程

    • 上面的例子中,change的过程,是改变了computed的依赖,之后的一系列流程
    • 触发computed的响应式依赖的dep.notify()方法,循环遍历该依赖的dep.subs数组中的每一个watcher.update()方法,在上面的例子中subs=[computed watcher, render watcher]所以先执行computed watcher
    • compued watcher
      • 在update方法中,会判断lazy=true?,因为computed watcher的lazy=true,所以执行 dirty=true
    • render watcher
      • 在update方法中, lazy不为true, 会执行 queueWatcher(this),就是调用渲染watcher重新求值和渲染

    资料

    实际案例-避坑 https://juejin.im/post/6844903705540558855
    详细 https://juejin.im/post/6844903606676799501
    源码版 https://juejin.im/post/6844903881583886349
    computed watcher https://juejin.im/post/6844903648984596494

    相关文章

      网友评论

          本文标题:[vue源码02] computed 响应式 - 初始化,访问,

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