精华内容
下载资源
问答
  • vue computed 原理

    2018-04-11 11:18:00
    vue computed 主要依靠数据依赖来更新,这里不展示computed源代码,只展示核心思想。 computed: { a(){ return this.b ++ } } data:{ b: 1 } vue中如果b变化,a也会变化。这儿为了简单,不在展示...

    vue computed 主要依靠数据依赖来更新,这里不展示computed源代码,只展示核心思想。

    computed: {
      a(){
         return this.b ++
       }  
    }
    data:{
      b: 1  
    }

    vue中如果b变化,a也会变化。这儿为了简单,不在展示computed.a的set跟get

    1、data中的数据需要使用es5中的 Object.defineProperty 设置set,get属性。

    2、在运行computed.a()函数的时候,需要建立数据依赖,搜集。

        // 做一个构造函数A,作用是对data添加特性方法(set,get)
        class A {
          constructor(data, computed) {
            this.defineProperty(data, 'a',data.a) // 对data中的属性‘a’添加特性方法
            this.collect =  computed.computA, // computed中的函数,记录下来
              
            computed.computA() // 运行此函数,会对data.a 进行取值,触发data.a的get函数,建立依赖
          }
    
          defineProperty(obj, key, val) { // 使用函数封装 添加特性方法
            const collect = [] 
            Object.defineProperty(obj, key, {
              get:()=> {                    // 当取值 data.a 时会触发get函数
                if (this.collect && !collect.some(it => it === this.collect)) {
                  collect.push(this.collect)  // 如果探测到有需要运行的compueted函数,搜集起来。
                }
                return val
              },
              set:value => {
                val = value
                collect.forEach(it => it())  // 如果data.a = 2 则运行set特性函数,此时,那些搜集起来的computed函数都运行
              }
            })
          }
        }
    


    const computed
    = { computA() {
         let result = data.a +1
         console.log(result)
    return result } } const data = { a: 1 } // 测试 new A(data, computed) // 2 data.a++ // 3 data.a = 6 //7

     

    转载于:https://www.cnblogs.com/gsgs/p/8794573.html

    展开全文
  • Vue computed 实现原理

    千次阅读 2019-03-03 16:35:47
    Vue computed 实现原理 Vue 2.5.17 1、Vue 在初始化的时候会 initState ,这个方法会初始化 props methods data computed watch // 4069 vm._self = vm; initLifecycle(vm); initEvents(vm); initRender(vm); ...

    Vue computed 实现原理

    Vue 2.5.17

    • 1、Vue 在初始化的时候会 initState ,这个方法会初始化 props methods data computed watch
    // 4069
    vm._self = vm;
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created');
    
    // 3303
    function initState (vm) {
      vm._watchers = [];
      var opts = vm.$options;
      if (opts.props) { initProps(vm, opts.props); }
      if (opts.methods) { initMethods(vm, opts.methods); }
      if (opts.data) {
        initData(vm);
      } else {
        observe(vm._data = {}, true /* asRootData */);
      }
      if (opts.computed) { initComputed(vm, opts.computed); }
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch);
      }
    }
    
    • 2、在初始化 computed 的时候首先会拿到对应的 get 方法。其次在非 SSR 下会创建对应的 watcher。随后对其进行初始化
    // 3422
    var computedWatcherOptions = { lazy: true };
    
    function initComputed(vm, computed) {
      var watchers = (vm._computedWatchers = Object.create(null))
      var isSSR = isServerRendering()
      for (var key in computed) {
        var userDef = computed[key]
        var getter = typeof userDef === 'function' ? userDef : userDef.get
        if ('development' !== 'production' && getter == null) {
          warn('Getter is missing for computed property "' + key + '".', vm)
        }
        if (!isSSR) {
          watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
        }
        if (!(key in vm)) {
          defineComputed(vm, key, userDef)
        } else {
          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)
          }
        }
      }
    }
    
    • 3、随后经过一系列的判断对其进行 Object.defineProperty 初始化。非 SSR 下会调用 createComputedGetter 方法对原始的 getter 进行加工
    // 3286
    var sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    };
    
    // 3465
    function defineComputed(target, key, userDef) {
      var shouldCache = !isServerRendering()
      if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef
        sharedPropertyDefinition.set = noop
      } else {
        sharedPropertyDefinition.get = userDef.get
          ? shouldCache && userDef.cache !== false
            ? createComputedGetter(key)
            : userDef.get
          : noop
        sharedPropertyDefinition.set = userDef.set ? userDef.set : noop
      }
      if ('development' !== '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)
    }
    
    • 4、我们来看一下 createComputedGetter 是如何加工的,在获取计算属性的值时候其实是拿的 watcher.value。在这里我们先不管 evaluate 和 depend 是干什么的我们先看一下 watcher 如何定义
    // 3489
    function createComputedGetter (key) {
      return function computedGetter () {
        var watcher = this._computedWatchers && this._computedWatchers[key];
        if (watcher) {
          if (watcher.dirty) {
            watcher.evaluate();
          }
          if (Dep.target) {
            watcher.depend();
          }
          return watcher.value
        }
      }
    }
    
    • 5、watcher 的 value 实际上是调用的 watcher.get 方法。这个方法有调用计算属性对应的 getter 方法。但我们在定义 watcher 的时候会传入 { lazy: true} ,这时候我们在定义 watcher 的时候不会触发 get 方法,而反过来看步骤 4,当 dirty = true 的时候首先会调用 watcher.evaluate()
    // 710
    Dep.target = null;
    var targetStack = [];
    function pushTarget (_target) {
      if (Dep.target) { targetStack.push(Dep.target); }
      Dep.target = _target;
    }
    function popTarget () {
      Dep.target = targetStack.pop();
    }
    
    
    // 3082
    var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
      // ...
      this.dirty = this.lazy;
      // ...
      this.value = this.lazy ? undefined : this.get()
    }
    
    // 3235
    Watcher.prototype.get = function get() {
      pushTarget(this);
      var value;
      var 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
    }
    
    • 6、当调用了 evaluate 方法就会计算一次 value,并将 dirty 置为 false。我们不妨设想一下:当我们的一开始的时候 dirty = true 会计算 value 随后 dirty = false,当我们的依赖项发生变化的时候将 dirty = true,这时候我们获取 value 的时候会重新计算。那么我们依赖项发生变化的时候是如何将对应的 dirty 设置为 true 的?
    // 3246
    Watcher.prototype.evaluate = function evaluate () {
      this.value = this.get();
      this.dirty = false;
    };
    
    • 7、我们在初始化 data 或者 props 的时候都会触发 defineReactive 方法
    // 966
    function defineReactive(obj, key, val, customSetter, shallow) {
      // ...
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
          var value = getter ? getter.call(obj) : val
          if (Dep.target) {
            dep.depend()
            if (childOb) {
              childOb.dep.depend()
              if (Array.isArray(value)) {
                dependArray(value)
              }
            }
          }
          return value
        },
        set: function reactiveSetter(newVal) {
          var value = getter ? getter.call(obj) : val
          /* eslint-disable no-self-compare */
          if (newVal === value || (newVal !== newVal && value !== value)) {
            return
          }
          /* eslint-enable no-self-compare */
          if ('development' !== 'production' && customSetter) {
            customSetter()
          }
          if (setter) {
            setter.call(obj, newVal)
          } else {
            val = newVal
          }
          childOb = !shallow && observe(newVal)
          dep.notify()
        }
      })
    }
    
    
    • 8、在我们调用 watcher.get 的时候会调用 pushTarget 方法,将 Dep.target 设置为当前的作用域(步骤 5 中的代码)。这时候我们调用计算属性的 getter。如果我们的计算属性依赖了 data 数据或者 prop 数据,就会触发其对应的 getter 方法。这时候就会触发依赖收集。
    dep.depend()
    
    // 693
    Dep.prototype.depend = function depend() {
      if (Dep.target) {
        Dep.target.addDep(this)
      }
    }
    
    // 3169
    Watcher.prototype.addDep = function addDep(dep) {
      var id = dep.id
      if (!this.newDepIds.has(id)) {
        this.newDepIds.add(id)
        this.newDeps.push(dep)
        if (!this.depIds.has(id)) {
          dep.addSub(this)
        }
      }
    }
    
    // 684
    Dep.prototype.addSub = function addSub(sub) {
      this.subs.push(sub)
    }
    
    
    • 9、我们在改变对应的依赖时候触发其 set 方法,dep.notify() 方法最终通知改变 dirty = true,从而重新计算 value
    dep.notify()
    
    // 699
    Dep.prototype.notify = function notify() {
      // stabilize the subscriber list first
      var subs = this.subs.slice()
      for (var i = 0, l = subs.length; i < l; i++) {
        subs[i].update()
      }
    }
    
    // 3200
    Watcher.prototype.update = function update() {
      /* istanbul ignore else */
      if (this.lazy) {
        this.dirty = true
      } else if (this.sync) {
        this.run()
      } else {
        queueWatcher(this)
      }
    }
    

    运行步骤

    • 初始化 cumputed 的时候会先初始化 props、data 等
    • 初始化 cumputed(!SSR) 时候,会添加其对应的 watcher(创建的时候不会触发 get 方法)。
    • 我们在获取计算属性的值的时候,如果 dirty = true 会调用 watcher.get 方法重新计算。随后 dirty = false
    • 在调用 get 方法的时候启动依赖收集,将 watcher 添加到对应依赖的 sub 里
    • 在依赖项发生变化的时候会调用 set 方法,最终会调用 watcher.update 方法将 dirty = true。这时候获取计算属性的时候会重新调用 watcher.get 方法

    总结

    • 我们在依赖项没有变化的时候,一直使用的是 watcher.value 也就是我们所说的缓存
    • 依赖项发生变化的时候重新计算一次 watcher.value

    补充

    • 10、我们在 computed 收集依赖完成之后,就会调用 cleanupDeps 方法,步骤 5。将 newDeps 中的依赖转存到 deps 中
    // 3176
    Watcher.prototype.cleanupDeps = function cleanupDeps () {
        var this$1 = this;
    
      var i = this.deps.length;
      while (i--) {
        var dep = this$1.deps[i];
        if (!this$1.newDepIds.has(dep.id)) {
          dep.removeSub(this$1);
        }
      }
      var 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;
    };
    
    // 689
    Dep.prototype.removeSub = function removeSub (sub) {
      remove(this.subs, sub);
    };
    
    • 11、我们通常一个 computed 会依赖另外一个 computed。假设 computedB 依赖了 computedA。当我们获取 computedB 的时候第一次调用 get。这时候(步骤 5) Dep.target === computedB -> computedB.getter -> computedA.getter 就会开启依赖收集,执行 computedA 的 watcher.depend()(步骤 4)。其中的 this$1.deps 就是 computedA 的依赖,这个方法将 computedB 的 watcher 添加到 computedA 的依赖中
    // 3254
    Watcher.prototype.depend = function depend () {
        var this$1 = this;
    
      var i = this.deps.length;
      while (i--) {
        this$1.deps[i].depend();
      }
    };
    
    • 12、这时候 computedA 的依赖添加了 computedA 的 watcher 和 computedB 的 watcher。computedB 的依赖添加 computedB 的 watcher。相关依赖触发了 set 会通知到对应的 computed watcher,从而触发重新计算
    • 13、当我们组件注销的时候同时会触发对应的注销函数,清除对应的 watcher
    // 2708
    if (vm._watcher) {
      vm._watcher.teardown();
    }
    var i = vm._watchers.length;
    while (i--) {
      vm._watchers[i].teardown();
    }
    
    // 3266
    Watcher.prototype.teardown = function teardown () {
        var this$1 = this;
    
      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);
        }
        var i = this.deps.length;
        while (i--) {
          this$1.deps[i].removeSub(this$1);
        }
        this.active = false;
      }
    };
    
    • 14、步骤9 中的 update 方法,在不同情况下会触发不同的方法,这里我们就不一一解释了。感兴趣的可以自己去看看
    展开全文
  • Computed 计算属性是 Vue 中常用的一个功能,本篇文章主要介绍了Vue Computed 计算属性原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • Vuecomputed原理分析

    2020-09-10 14:40:24
    1.Computed也是响应式的 Computed是响应式的,读取Computed会触发get,设置Computed会触发set 2.Computed如何控制缓存 计算属性是有缓存的,比如某个计算属性C,它依赖data中的A,如果没有缓存的话,每次读取C时,C...

    1.Computed也是响应式的

    Computed是响应式的,读取Computed会触发get,设置Computed会触发set

    2.Computed如何控制缓存

    计算属性是有缓存的,比如某个计算属性C,它依赖data中的A,如果没有缓存的话,每次读取C时,C都回去读取A,从而触发A的get。多次触发A的get有时候是一个非常消耗性能的操作。所以Computed必须要有缓存。

    computed里面控制缓存最重要的一点就是脏数据标记为dirty, dirty是watcher的一个属性。

    • 当dirty为true时,读取computed会重新计算
    • 当dirty为false时,读取computed会使用缓存

    3. 依赖的data发生变化,computed是如何更新的

    页面P依赖计算属性C, 计算属性C又依赖data里面的A, computed更新步骤如下:

    1. 由于C依赖了A, A可以收集到C的watcher
    2. 当A发生变化时,会将watcher的脏数据标记位dirty设置为true
    3. 并且A会收集到页面P的watcher,A通知P进行更新,从而页面P重新读取计算属性C, 由于此时dirty为true,此时的计算属性会重新计算。
    4. computed更新完毕,重新将脏数据标记位dirty设置为false,如果其依赖的A不发生改变,那下次再进入就会读取缓存。

    4. 计算属性C是如何让data中的A收集到页面P的watcher的

    这其实是计算属性中一个非常巧妙的操作。来看一下核心的源码(已简化)

    function createComputedGetter(key) {
        return function() { 
            // 获取到相应 key 的 computed-watcher
            var watcher = this._computedWatchers[key];
            // 如果 computed 依赖的数据变化,dirty 会变成true,
            // 从而重新计算,然后更新缓存值 watcher.value
            if (watcher.dirty) {
                watcher.evaluate();
            }        
            // 这里是 月老computed 牵线的重点,让双方建立关系
            if (Dep.target) {
                watcher.depend();
            }        
            return watcher.value
        }
    }
    
    展开全文
  • 在这之前,希望你能够对响应式原理有一些理解,因为 computed 是基于响应式原理进行工作。如果你对响应式原理还不是很了解,可以阅读我的上一篇文章:手摸手带你理解Vue响应式原理 computed 用法 想要理解原理,最...

    前言

    computedVue 中是很常用的属性配置,它能够随着依赖属性的变化而变化,为我们带来很大便利。那么本文就来带大家全面理解 computed 的内部原理以及工作流程。

    在这之前,希望你能够对响应式原理有一些理解,因为 computed 是基于响应式原理进行工作。如果你对响应式原理还不是很了解,可以阅读我的上一篇文章:手摸手带你理解Vue响应式原理

    computed 用法

    想要理解原理,最基本就是要知道如何使用,这对于后面的理解有一定的帮助。

    第一种,函数声明:

    var vm = new Vue({
      el: '#example',
      data: {
        message: 'Hello'
      },
      computed: {
        // 计算属性的 getter
        reversedMessage: function () {
          // `this` 指向 vm 实例
          return this.message.split('').reverse().join('')
        }
      }
    })
    复制代码

    第二种,对象声明:

    computed: {
      fullName: {
        // getter
        get: function () {
          return this.firstName + ' ' + this.lastName
        },
        // setter
        set: function (newValue) {
          var names = newValue.split(' ')
          this.firstName = names[0]
          this.lastName = names[names.length - 1]
        }
      }
    }
    复制代码

    温馨提示:computed 内使用的 data 属性,下文统称为“依赖属性”

    工作流程

    先来了解下 computed 的大概流程,看看计算属性的核心点是什么。

    入口文件:

    // 源码位置:/src/core/instance/index.js
    import { initMixin } from './init'
    import { stateMixin } from './state'
    import { renderMixin } from './render'
    import { eventsMixin } from './events'
    import { lifecycleMixin } from './lifecycle'
    import { warn } from '../util/index'
    
    function Vue (options) {
      this._init(options)
    }
    
    initMixin(Vue)
    stateMixin(Vue)
    eventsMixin(Vue)
    lifecycleMixin(Vue)
    renderMixin(Vue)
    
    export default Vue
    复制代码

    _init:

    // 源码位置:/src/core/instance/init.js
    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
        vm._uid = uid++
    
        // merge options
        if (options && options._isComponent) {
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options)
        } else {
          // mergeOptions 对 mixin 选项和 new Vue 传入的 options 选项进行合并
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }
    
        // expose real self
        vm._self = vm
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        callHook(vm, 'beforeCreate')
        initInjections(vm) // resolve injections before data/props
        // 初始化数据
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created')
    
        if (vm.$options.el) {
          vm.$mount(vm.$options.el)
        }
      }
    }
    复制代码

    initState:

    // 源码位置:/src/core/instance/state.js 
    export function initState (vm: Component) {
      vm._watchers = []
      const opts = vm.$options
      if (opts.props) initProps(vm, opts.props)
      if (opts.methods) initMethods(vm, opts.methods)
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data = {}, true /* asRootData */)
      }
      // 这里会初始化 Computed
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    复制代码

    initComputed:

    // 源码位置:/src/core/instance/state.js 
    function initComputed (vm: Component, computed: Object) {
      // $flow-disable-line
      // 1
      const watchers = vm._computedWatchers = Object.create(null)
      // computed properties are just getters during SSR
      const isSSR = isServerRendering()
        
      for (const key in computed) {
        const userDef = computed[key]
        // 2
        const getter = typeof userDef === 'function' ? userDef : userDef.get
    
        if (!isSSR) {
          // create internal watcher for the computed property.
          // 3
          watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            { lazy: true }
          )
        }
    
        // component-defined computed properties are already defined on the
        // component prototype. We only need to define computed properties defined
        // at instantiation here.
        if (!(key in vm)) {
          // 4
          defineComputed(vm, key, userDef)
        }
      }
    }
    复制代码
    1. 实例上定义 _computedWatchers 对象,用于存储“计算属性Watcher
    2. 获取计算属性的 getter,需要判断是函数声明还是对象声明
    3. 创建“计算属性Watcher”,getter 作为参数传入,它会在依赖属性更新时进行调用,并对计算属性重新取值。需要注意 Watcherlazy 配置,这是实现缓存的标识
    4. defineComputed 对计算属性进行数据劫持

    defineComputed:

    // 源码位置:/src/core/instance/state.js 
    const noop = function() {}
    // 1
    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }
    
    export function defineComputed (
      target: any,
      key: string,
      userDef: Object | Function
    ) {
      // 判断是否为服务端渲染
      const shouldCache = !isServerRendering()
      if (typeof userDef === 'function') {
        // 2
        sharedPropertyDefinition.get = shouldCache
          ? createComputedGetter(key)
          : createGetterInvoker(userDef)
        sharedPropertyDefinition.set = noop
      } else {
        // 3
        sharedPropertyDefinition.get = userDef.get
          ? shouldCache && userDef.cache !== false
            ? createComputedGetter(key)
            : createGetterInvoker(userDef.get)
          : noop
        sharedPropertyDefinition.set = userDef.set || noop
      }
      // 4
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    复制代码
    1. sharedPropertyDefinition 是计算属性初始的属性描述对象
    2. 计算属性使用函数声明时,设置属性描述对象的 getset
    3. 计算属性使用对象声明时,设置属性描述对象的 getset
    4. 对计算属性进行数据劫持,sharedPropertyDefinition 作为第三个给参数传入

    客户端渲染使用 createComputedGetter 创建 get,服务端渲染使用 createGetterInvoker 创建 get。它们两者有很大的不同,服务端渲染不会对计算属性缓存,而是直接求值:

    function createGetterInvoker(fn) {
      return function computedGetter () {
        return fn.call(this, this)
      }
    }
    复制代码

    但我们平常更多的是讨论客户端渲染,下面看看 createComputedGetter 的实现。

    createComputedGetter:

    // 源码位置:/src/core/instance/state.js
    function createComputedGetter (key) {
      return function computedGetter () {
        // 1
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
          // 2
          if (watcher.dirty) {
            watcher.evaluate()
          }
          // 3
          if (Dep.target) {
            watcher.depend()
          }
          // 4
          return watcher.value
        }
      }
    }
    复制代码

    这里就是计算属性的实现核心,computedGetter 也就是计算属性进行数据劫持时触发的 get

    1. 在上面的 initComputed 函数中,“计算属性Watcher”就存储在实例的_computedWatchers上,这里取出对应的“计算属性Watcher
    2. watcher.dirty 是实现计算属性缓存的触发点,watcher.evaluate 对计算属性重新求值
    3. 依赖属性收集“渲染Watcher
    4. 计算属性求值后会将值存储在 value 中,get 返回计算属性的值

    计算属性缓存及更新

    缓存

    下面我们来将 createComputedGetter 拆分,分析它们单独的工作流程。这是缓存的触发点:

    if (watcher.dirty) {
      watcher.evaluate()
    }
    复制代码

    接下来看看 Watcher 相关实现:

    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
        // 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
        // dirty 初始值等同于 lazy
        this.dirty = this.lazy // for lazy watchers
        this.deps = []
        this.newDeps = []
        this.depIds = new Set()
        this.newDepIds = new Set()
        this.value = this.lazy
          ? undefined
          : this.get()
      }
    }
    复制代码

    还记得创建“计算属性Watcher”,配置的 lazy 为 true。dirty 的初始值等同于 lazy。所以在初始化页面渲染,对计算属性进行取值时,会执行一次 watcher.evaluate

    evaluate() {
      this.value = this.get()
      this.dirty = false
    }
    复制代码

    求值后将值赋给 this.value,上面 createComputedGetter 内的 watcher.value 就是在这里更新。接着 dirty 置为 false,如果依赖属性没有变化,下一次取值时,是不会执行 watcher.evaluate 的, 而是直接就返回 watcher.value,这样就实现了缓存机制。

    更新

    依赖属性在更新时,会调用 dep.notify:

    notify() {
      this.subs.forEach(watcher => watcher.update())
    }
    复制代码

    然后执行 watcher.update:

    update() {
      if (this.lazy) {
        this.dirty = true
      } else if (this.sync) {
        this.run()
      } else {
        queueWatcher(this)
      }
    }
    复制代码

    由于“计算属性Watcher”的 lazy 为 true,这里 dirty 会置为 true。等到页面渲染对计算属性取值时,符合触发点条件,执行 watcher.evaluate 重新求值,计算属性随之更新。

    依赖属性收集依赖

    收集计算属性Watcher

    初始化时,页面渲染会将“渲染Watcher”入栈,并挂载到Dep.target

    在页面渲染过程中遇到计算属性,对其取值,因此执行 watcher.evaluate 的逻辑,接着调用 this.get:

    get () {
      // 1
      pushTarget(this)
      let value
      const vm = this.vm
      try {
        // 2
        value = this.getter.call(vm, vm) // 计算属性求值
      } catch (e) {
        if (this.user) {
          handleError(e, vm, `getter for watcher "${this.expression}"`)
        } else {
          throw e
        }
      } finally {
        popTarget()
        this.cleanupDeps()
      }
      return value
    }
    复制代码
    Dep.target = null
    let stack = []  // 存储 watcher 的栈
    
    export function pushTarget(watcher) {
      stack.push(watcher)
      Dep.target = watcher
    } 
    
    export function popTarget(){
      stack.pop()
      Dep.target = stack[stack.length - 1]
    }
    复制代码

    pushTarget 轮到“计算属性Watcher”入栈,并挂载到Dep.target,此时栈中为 [渲染Watcher, 计算属性Watcher]

    this.getter 对计算属性求值,在获取依赖属性时,触发依赖属性的 数据劫持get,执行 dep.depend 收集依赖(“计算属性Watcher”)

    收集渲染Watcher

    this.getter 求值完成后popTragte,“计算属性Watcher”出栈,Dep.target 设置为“渲染Watcher”,此时的 Dep.target 是“渲染Watcher

    if (Dep.target) {
      watcher.depend()
    }
    复制代码

    watcher.depend 收集依赖:

    depend() {
      let i = this.deps.length
      while (i--) {
        this.deps[i].depend()
      }
    }
    复制代码

    deps 内存储的是依赖属性的 dep,这一步是依赖属性收集依赖(“渲染Watcher”)

    经过上面两次收集依赖后,依赖属性的 subs 存储两个 Watcher,[计算属性Watcher,渲染Watcher]

    为什么依赖属性要收集渲染Watcher

    我在初次阅读源码时,很奇怪的是依赖属性收集到“计算属性Watcher”不就好了吗?为什么依赖属性还要收集“渲染Watcher”?

    第一种场景:模板里同时用到依赖属性和计算属性

    <template>
      <div>{{msg}} {{msg1}}</div>
    </template>
    
    export default {
      data(){
        return {
          msg: 'hello'
        }
      },
      computed:{
        msg1(){
          return this.msg + ' world'      
        }
      }
    }
    复制代码

    模板有用到依赖属性,在页面渲染对依赖属性取值时,依赖属性就存储了“渲染Watcher”,所以 watcher.depend 这步是属于重复收集的,但 watcher 内部会去重。

    这也是我为什么会产生疑问的点,Vue 作为一个优秀的框架,这么做肯定有它的道理。于是我想到了另一个场景能合理解释 watcher.depend 的作用。

    第二种场景:模板内只用到计算属性

    <template>
      <div>{{msg1}}</div>
    </template>
    
    export default {
      data(){
        return {
          msg: 'hello'
        }
      },
      computed:{
        msg1(){
          return this.msg + ' world'      
        }
      }
    }
    复制代码

    模板上没有使用到依赖属性,页面渲染时,那么依赖属性是不会收集 “渲染Watcher”的。此时依赖属性里只会有“计算属性Watcher”,当依赖属性被修改,只会触发“计算属性Watcher”的 update。而计算属性的 update 里仅仅是将 dirty 设置为 true,并没有立刻求值,那么计算属性也不会被更新。

    所以需要收集“渲染Watcher”,在执行完“计算属性Watcher”后,再执行“渲染Watcher”。页面渲染对计算属性取值,执行 watcher.evaluate 才会重新计算求值,页面计算属性更新。

    总结

    计算属性原理和响应式原理都是大同小异的,同样的是使用数据劫持以及依赖收集,不同的是计算属性有做缓存优化,只有在依赖属性变化时才会重新求值,其它情况都是直接返回缓存值。服务端不对计算属性缓存。

    计算属性更新的前提需要“渲染Watcher”的配合,因此依赖属性的 subs 中至少会存储两个 Watcher


    作者:WahFung
    链接:https://juejin.im/post/6844904199596015624
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    展开全文
  • vue computed 和 vm.$data 原理

    万次阅读 2020-07-07 09:49:43
    使用vuex中store中的数据,基本上离不开vue中一个常用的属性computed。官方一个最简单的例子如下 var vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { // 计算属性的 getter ...
  • 源码分析vue computed

    2021-04-05 00:37:17
    在阅读本文之前,建议先了解vue响应式原理和watch侦听器相关原理,可以看一下源码分析vue响应式原理 和源码分析vue watch侦听器,将有助理解computed。 用法示例 var vm = new Vue({ el: '#app', data: { ...
  • computed原理Vue中的data定义的参数使用发布订阅模式初始化后,开始解析computed属性 1. 对每个computed属性建立专门的Watcher 2. computed属性方法中访问的每一个data参数,都会调用参数的get方法,这个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 440
精华内容 176
关键字:

vuecomputed原理

vue 订阅