精华内容
下载资源
问答
  • computed原理解析图如下: 首先在vue的src/core/instance/index.js里,会通过如下调用步骤: initMixin->initState->initComputed 进入到initComputed函数里。 在initComputed函数里,会初始化用户定义的所有...


    阅读本文前题是,已经对vue数据绑定机制中的的observer和watcher原理有个大致的了解,只要知道一个watcher去get一个data时,就会建立一个互相绑定的关系,data的会把watcher存人dep.subs数组,知道都有谁在watch它,以便发生变化时通知,而watcher也会把data的dep放入自己的deps数据,知道自己依赖哪些数据。

    在此基础上,本文进一步讲解一下computed的原理,整体流程图如下,可对照此图理解后面的内容:

    在这里插入图片描述

    1. computed初始化

    在初始化组件创建组件实例时,会调用到initComputed函数,这里会初始化组件里定义的computed属性,代码如下:

    // vue/src/core/instance/state.js
    const computedWatcherOptions = { lazy: true }
    
    function initComputed (vm: Component, computed: Object) {
      // $flow-disable-line
      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]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        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.
          watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions
          )
        }
        
        // 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)) {
          defineComputed(vm, key, userDef)
        } else if (process.env.NODE_ENV !== 'production') {
          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)
          }
        }
      }
    }
    

    在for循环中,针对每一个computed属性,会做两件事:创建watcher,以及compuetdGetter(即vm.sum的getter)

    1.1. 创建watcher

    ...
    watchers[key] = new Watcher(
    	vm,
    	getter || noop,
        noop,
        computedWatcherOptions
    )
    ...
    

    getter值即为用户定义的computed属性,如本示例中的sum:

    // myproject/src/views/myview.js
    computed: {
    	sum() {
    		return this.a + this.b
    	}
    }
    

    options的lazy值为true,这个值会同时传给watcher的dirty,这两个值初始化为true,决定了computed watcher和render watcher (负责监听数据变化,并更新组件视图的watcher) 在数据发生变化时,响应执行过程的不同,这个后面会讲到。

    1.2. 创建computedGetter

    在vm实例上为sum创建一个computedGetter,即vm.sum的getter,在initComputed中通过如下调用步骤进入到computedGetter的定义:

    defineComputed -> createComputedGetter

    // vue/src/core/instance/state.js
    ...
    function initComputed (vm: Component, computed: Object) {
    	...
    	if (!(key in vm)) {
    		defineComputed(vm, key, userDef)
    	}
    	...
    }
    
    export function defineComputed (target: any, key: string, userDef: Object | Function) {
    	...
    	createComputedGetter(key)
    	...
    }
    
    function createComputedGetter (key) {
      return function computedGetter () {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
          if (watcher.dirty) {
            watcher.evaluate()
          }
          if (Dep.target) {
            watcher.depend()
          }
          return watcher.value
        }
      }
    }
    

    以上computed初始化工作就完成了,此时这个function computedGetter () {…}里的内容还并未执行,这时watcher.value还并没有被计算得到值,而没有去计算值,也就没有去调用data的getter去取data,所以此时computed watcher和data也还没有绑定依赖,这个值的变化要关注下面两个过程:

    1. 当data发生变化时,watcher.value依然不会马上计算出值,只会去设置watcher.dirty为true,标识watcher.value的是个脏值,已经过时了,别人再来取我时,我需要重新计算。前面讲到computed watcher初始化时dirty为true,所以初始化完成后,watcher.value就已经是一个脏值,因为还没有执行过计算得到值。
    2. 组件mount时,创建render watcher去初次get vm.sum这个computed属性时,执行computedGetter的代码,才会通过watcher.evaluate()去计算得到watcher.value的值,并把dirty设置为false,而evaluate的过程会去取data,就会让computed watcher和所需的data绑定依赖。

    这里还要关注一个问题,就是data的变化会通知computed watcher去设置dirty,就是上面第1步,但是render watcher是怎么知道数据变化,并去取vm.sum,即上面第2步。
    这是借助上面computedGetter函数里的watcher.depend(),以computed watcher为桥梁,让render watcher和data之间做了一个依赖绑定。

    接下来就详细讲解这几个过程在代码里是怎么做的。

    2. computed初次计算及依赖绑定

    2.1 组件mount创建render watcher触发computedGetter

    组件mount时,会创建一个render watcher,传入的expOrFn参数是一个更新组件的函数updateComponent:

    // vue/src/core/instance/lifecycle.js
    export function mountComponent (
    	...
     	updateComponent = () => {
          vm._update(vm._render(), hydrating)
        }
        ...
        new Watcher(vm, updateComponent, noop, {
        before () {
          if (vm._isMounted && !vm._isDestroyed) {
            callHook(vm, 'beforeUpdate')
          }
        }
      }, true /* isRenderWatcher */)
      ...
    }
    

    render watcher的构造函数中,this.getter值得到的就是updateComponent这个函数,然后会主动去调用一自己的get函数,最终进入到updateComponent函数。

    // vue/src/core/observer/watcher.js
    export default class Watcher {
    	constructor(vm, expOrFn, cb, options, isRenderWatcher) {
    	...
    	if (typeof expOrFn === 'function') {
          this.getter = expOrFn
        }
        ...
    	// render watcher lazy 为 false, computed watcher 为 true
    	this.value = this.lazy ? undefined : this.get()
    	...
        }
        ...
    
    	get() {
    		value = this.getter.call(vm, vm)
    	}
    }
    

    进入到updateComponent后,会调用vm._render函数,然后进入组件编译生成的render函数:

    var render = function() {
      var _vm = this
      var _h = _vm.$createElement
      var _c = _vm._self._c || _h
      return _c("div", [_vm._v(_vm._s(_vm.sum) + " ")])
    }
    

    因为组件模板中用到了{{ sum }},所以render函数中会引用到vm.sum这个值:
    便会调用到vm.sum的computedGetter函数。

    // vue/src/core/instance/state.js
    function createComputedGetter (key) {
      return function computedGetter () {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
          if (watcher.dirty) {
            watcher.evaluate()
          }
          if (Dep.target) {
            watcher.depend()
          }
          return watcher.value
        }
      }
    }
    

    此时dirty还是初始的赋值true,就会去调evaluate

    2.2. computed初次计算及computed watcher的依赖绑定

    // vue/src/core/observer/watcher.js
    export default class Watcher {
    	...
    	evaluate () {
        	this.value = this.get()
    		this.dirty = false
        }
        ...
        
    	get() {
    		...
    		value = this.getter.call(vm, vm)
    		...
    		return value
    	}
    }
    

    evaluate函数里,会调用computed watcher的getter,前面讲到,getter就是那this.a + this.b函数,进而就会去调到a和b的getter函数,这里就把computed watcher和a,b做了依赖绑定。

    接下来把计算的值给了this.value,这便是computed的缓存值,并把dirty设置为false,如果下次数据没有更新,dirty为false,就会不调用evaluate,而是直接取这个值。

    2.3. render watcher的依赖绑定

    evaluate结束之后,接下来会去调用vm.sum的computed watcher的depend()

    // vue/src/core/observer/watcher.js
    export default class Watcher {
    	...
    	depend () {
    		let i = this.deps.length
    		while (i--) {
    		this.deps[i].depend()
    	}
    	...
    }
    

    此时的Dep.target是render watcher,这里的this.deps,就是vm.sum的computed watcher刚绑定的依赖data.a和data.b的dep。

    computed watcher的depend函数就实现了以自已为桥梁,让render watcher和data.a,data.b建立了绑定。

    前面那个问题为什么render watcher没有直接引用data,却能知道data的变化,答案就在这里。

    至此vm.sum的computed watcher已计算出了初始值,computed watcher和所依赖的data之间,render watcher和所依赖的data之间,也都进行了绑定,并且computed watcher的dirty值设为了false,watcher.value缓存了计算结果,下次取vm.sum的值不再evaluate计算,直接返回watcher.value值,除非data变化了。

    3. data 变化通知 watcher 更新

    通过上一节我们知道了,computed watcher和render watcher都绑定了对应的data,当data变化时,便会通知两个watcher去更新。

    // vue/src/core/observer/watcher.js
    export default class Watcher {
      ...
      update () {
        /* istanbul ignore else */
        if (this.lazy) {
          this.dirty = true
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
      }
      ...
    }
    

    3.1. computed watcher 更新

    由于computed watcher由于lazy值为true,所以update过程很简单,就是将dirty设置为true,vm.sum再被get时,就会执行第2节中的一系列过程,重新走一遍计算新值和绑定依赖。

    3.2. render watcher 更新

    render watcher的更新则是一个异步操作,先通过queueWatcher,将自己添加到队列中:

    // vue/src/core/observer/scheduler.js
    export function queueWatcher (watcher: Watcher) {
    	...
        queue.push(watcher)
        ...
        if (!waiting) {
          	waiting = true
    	  	...
          	nextTick(flushSchedulerQueue)
        }
    }
    ...
    

    然后异步的在下一个时钟nextTick里执行flushSchedulerQueue,这就保证了,computed watcher的this.dirty = true的本时钟内同步赋值,会先于render watcher异步更新,而如果render watcher先更新,computed watcher的dirty还没有改为true,那么render watcher访问vm.sum时的computedGetter时,拿到的就是缓存的旧值。

    nextTick会将flushSchedulerQueue放到一个callbacks数据组里,通过一个异步方法timerFunc,依次执行这些callbacks,这个异步过程的实现有4种,按照如下顺序依次检测哪种可用就用哪种:

    1. Promise
    2. MutationObserver
    3. setImmediate
    4. setTimeout

    在下一个时钟,执行flushSchekulerQueue过程如下:

    // vue/src/core/observer/scheduler.js
    function flushSchedulerQueue () {
    	...
    	for (index = 0; index < queue.length; index++) {
    		...
    		watcher.run()
    		...
    	}
    }
    
    // vue/src/core/observer/watcher.js
    export default class Watcher {
      	...
      	run () {
      		...
          	const value = this.get()
          	...
      	}
      	...
    }
    

    这里便又会去执行get过程,把第2节中的内容再次走一遍。

    展开全文
  • computed原理】 Vue中的data定义的参数使用发布订阅模式初始化后,开始解析computed属性 1. 对每个computed属性建立专门的Watcher 2. computed属性方法中访问的每一个data参数,都会调用参数的get方法,这个...

    【computed原理】

    Vue中的data定义的参数使用发布订阅模式初始化后,开始解析computed属性

    1. 对每个computed属性建立专门的Watcher

    2. computed属性方法中访问的每一个data参数,都会调用参数的get方法,这个时候,将参数的dep加入Watcher,同时dep的subs添加Watcher

    3. 当我们改变对应参数的值的时候,会调用参数的set方法,触发dep的notify方法,将Watcher设置为dirty

    4. 当访问computed属性,触发get方法的时候,会发现dirty被触发,则重新获取新的值(必须触发get方法才会去获取新的值)

    【Vuex原理】

    Vuex利用了computed的原理,相当于注册了一个无template的组件,store则是组件中的data,当我们安装Vuex组件的时候,install方法中已经在beforeCreate生命周期的时候注册了全局store,当改变store的值的时候,利用set方法的notify触发computed属性dirty,获取值的时候调用get方法获取最新的值

    【参考文档】

    https://www.jianshu.com/p/d95a7b8afa06

    https://zhuanlan.zhihu.com/p/71132210

    展开全文
  • 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原理分析

    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 用法 想要理解原理,最...
  • 一、 什么是computed定义: 当其依赖的属性的值发生变化的时,计算属性会重新计算。反之则使用缓存中的属性值, 其设计的目的就是为了解决模板中放入太多的逻辑而导致模板过重且难以维护的...
  • 今天主要是讨论 computed 实现原理computed在内部主要是运用 Watcher和 Dep构造函数进行收集依赖和派发更新。 咱们先来看看 Watcher和 Dep源码。 var uid = 0; /** * dep 就是用来给每...
  • 之前的文章详细的介绍了vue3.0 相应式原理,知道了用proxy代替Object.defineProperty 的利与弊,了解了依赖收集和派发更新的大致流程,知道了vue3.0响应式原理,这节我们一起研究vue3.0中的 watch 有那些变化。...

空空如也

空空如也

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

computed原理