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 的重新计算

  • 相关阅读:
    16.检查是否为BST
    15.输出单层结点
    14.高度最小的BST
    *13.有向路径检查
    12.二叉树平衡检查
    11.双栈排序
    10.回文链表
    9.链式A+B
    8.链表分割
    7.访问单个节点的删除
  • 原文地址:https://www.cnblogs.com/xuange306/p/9235131.html
Copyright © 2011-2022 走看看