zoukankan      html  css  js  c++  java
  • 深入理解 Vue Computed 计算属性

    Computed 计算属性是 Vue 中常用的一个功能,我们今天来说一下他的执行过长

    拿官网简单的例子来看一下:

    <div id="example">
      <p>Original message: "{{ message }}"</p>
      <p>Computed reversed message: "{{ reversedMessage }}"</p>
    </div>
    
    
    
    
    var vm = new Vue({
      el: '#example',
      data: {
        message: 'Hello'
      },
      computed: {
        // a computed getter
        reversedMessage: function () {
          // `this` points to the vm instance
          return this.message.split('').reverse().join('')
        }
      }
    })
    

      

    Vue 源码分析 Computed 的实现原理

    data 属性初始化 getter setter:

    // src/observer/index.js
    
    // 这里开始转换 data 的 getter setter,原始值已存入到 __ob__ 属性中
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        const value = getter ? getter.call(obj) : val
        // 判断是否处于依赖收集状态
        if (Dep.target) {
          // 建立依赖关系
          dep.depend()
          ...
        }
        return value
      },
      set: function reactiveSetter (newVal) {
        ...
        // 依赖发生变化,通知到计算属性重新计算
        dep.notify()
      }
    })

    computed 计算属性初始化

    // src/core/instance/state.js
    
    // 初始化计算属性
    function initComputed (vm: Component, computed: Object) {
      ...
      // 遍历 computed 计算属性
      for (const key in computed) {
        ...
        // 创建 Watcher 实例
        // create internal watcher for the computed property.
        watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
    
        // 创建属性 vm.reversedMessage,并将提供的函数将用作属性 vm.reversedMessage 的 getter,
        // 最终 computed 与 data 会一起混合到 vm 下,所以当 computed 与 data 存在重名属性时会抛出警告
        defineComputed(vm, key, userDef)
        ...
      }
    }
    
    export function defineComputed (target: any, key: string, userDef: Object | Function) {
      ...
      // 创建 get set 方法
      sharedPropertyDefinition.get = createComputedGetter(key)
      sharedPropertyDefinition.set = noop
      ...
      // 创建属性 vm.reversedMessage,并初始化 getter setter
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    
    function createComputedGetter (key) {
      return function computedGetter () {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
          if (watcher.dirty) {
            // watcher 暴露 evaluate 方法用于取值操作
            watcher.evaluate()
          }
          // 同第1步,判断是否处于依赖收集状态
          if (Dep.target) {
            watcher.depend()
          }
          return watcher.value
        }
      }
    }

    无论是属性还是计算属性,都会生成一个对应的 watcher 实例。

    // src/core/observer/watcher.js
    
    // 当通过 vm.reversedMessage 获取计算属性时,就会进到这个 getter 方法
    get () {
      // this 指的是 watcher 实例
      // 将当前 watcher 实例暂存到 Dep.target,这就表示开启了依赖收集任务
      pushTarget(this)
      let value
      const vm = this.vm
      try {
        // 在执行 vm.reversedMessage 的函调函数时,会触发属性(步骤1)和计算属性(步骤2)的 getter
        // 在这个执行过程中,就可以收集到 vm.reversedMessage 的依赖了
        value = this.getter.call(vm, vm)
      } catch (e) {
        if (this.user) {
          handleError(e, vm, `getter for watcher "${this.expression}"`)
        } else {
          throw e
        }
      } finally {
        if (this.deep) {
          traverse(value)
        }
        // 结束依赖收集任务
        popTarget()
        this.cleanupDeps()
      }
      return value
    }

    上面多出提到了 dep.depend, dep.notify, Dep.target,那么 Dep 究竟是什么呢?

    Dep 的代码短小精悍,但却承担着非常重要的依赖收集环节。

    // src/core/observer/dep.js
    
    export default class Dep {
      static target: ?Watcher;
      id: number;
      subs: Array<Watcher>;
    
      constructor () {
        this.id = uid++
        this.subs = []
      }
    
      addSub (sub: Watcher) {
        this.subs.push(sub)
      }
    
      removeSub (sub: Watcher) {
        remove(this.subs, sub)
      }
    
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
    
      notify () {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          // 更新 watcher 的值,与 watcher.evaluate() 类似,
          // 但 update 是给依赖变化时使用的,包含对 watch 的处理
          subs[i].update()
        }
      }
    }
    
    // 当首次计算 computed 属性的值时,Dep 将会在计算期间对依赖进行收集
    Dep.target = null
    const targetStack = []
    
    export function pushTarget (_target: Watcher) {
      // 在一次依赖收集期间,如果有其他依赖收集任务开始(比如:当前 computed 计算属性嵌套其他 computed 计算属性),
      // 那么将会把当前 target 暂存到 targetStack,先进行其他 target 的依赖收集,
      if (Dep.target) targetStack.push(Dep.target)
      Dep.target = _target
    }
    
    export function popTarget () {
      // 当嵌套的依赖收集任务完成后,将 target 恢复为上一层的 Watcher,并继续做依赖收集
      Dep.target = targetStack.pop()
    }

    总结

    1. data 属性初始化 getter setter

    2. computed 计算属性初始化,提供的函数将用作属性 vm.reversedMessage 的 getter

    3. 当首次获取 reversedMessage 计算属性的值时,Dep 开始依赖收集

    4. 在执行 message getter 方法时,如果 Dep 处于依赖收集状态,则判定 message 为 reversedMessage 的依赖,并建立依赖关系

    5. 当 message 发生变化时,根据依赖关系,触发 reverseMessage 的重新计算

  • 相关阅读:
    用Python完成一个汇率转换器
    鸿蒙如何用JS开发智能手表App
    鸿蒙如何用JS开发智能手表App
    SAP Spartacus SplitViewComponent Migration 的一个具体例子
    SAP Spartacus B2B 页面 Popover Component 的条件显示逻辑
    SAP Spartacus 升级时关于 schematics 的更新
    SAP Spartacus B2B 页面 Disable 按钮的显示原理
    SAP Spartacus B2B 页面 Disable Confirmation 对话框的显示原理
    通过 Feature Level 动态控制 SAP Spartacus 的页面显示
    SAP Commerce Cloud Build Manifest Components
  • 原文地址:https://www.cnblogs.com/shuiche/p/9235131.html
Copyright © 2011-2022 走看看