computed 函数调用 收集依赖总结

1,161 阅读2分钟
  • 我们知道,计算属性在触发get的时候会收集依赖
  • 对于计算属性的函数调用?依赖又是怎么收集的,以及细节处理?
initComputed -> 创建Watch响应式 
//-> 设置其getter:Object.defineProperty(target, key, sharedPropertyDefinition);
// 用于访问该属性时,触发的动作
// 代码如下

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
    }
  }
}

1、对于问题1:computed属性,具有惰性求值的优势

2、 对于模板中使用到的属性方法等,Vue其实也是会收集其依赖,一旦依赖改变,就会触发_update

// 访问this.$data  找到对应的get
set otherMsg: ƒ reactiveSetter(newVal)
arguments: (...)
caller: (...)
length: 1
name: "reactiveSetter"
prototype: {constructor: ƒ}
__proto__: ƒ ()
[[FunctionLocation]]: vue.runtime.esm.js?2b0e:1037
[[Scopes]]: Scopes[4]
---------------------------
然后执行 dep.notify();
Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  if (process.env.NODE_ENV !== 'production' && !config.async) {
    // subs aren't sorted in scheduler if not running async
    // we need to sort them now to make sure they fire in correct
    // order
    subs.sort(function (a, b) { return a.id - b.id; });
  }
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};
------------------------
// 以下时打印观察者subs其中一项的内容
active: true
before: ƒ before()
cb: ƒ noop(a, b, c)
deep: false
depIds: Set(11) {6, 7, 8, 12, 16, …}
deps: (11) [Dep, Dep, Dep, Dep, Dep, Dep, Dep, Dep, Dep, Dep, Dep]
dirty: false
expression: "function () {↵      vm._update(vm._render(), hydrating);↵    }"
getter: ƒ ()
id: 2
lazy: false
newDepIds: Set(0) {}
newDeps: []
sync: false
user: false
value: undefined
vm: VueComponent {_uid: 1, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
__proto__: Object

// 由此可知
模板中的变量,在渲染的时候,其实也是会去收集依赖,然后一旦变量改变,就会触发
function () {↵      vm._update(vm._render(), hydrating);↵    }
// 我们通过这个也可以用于调试看某个变量是否会触发视图的更新

对于问题2:如果我们在模板中{{ computedMsg(item) }} v-for中这个场景还是比较常见的

<template>
    <ul>
        <li v-for="(item,index) in list" :key="index">
            {{ computedMsg(item) }}
        </li>
    </ul>
</template>
<script>
export default {
    name:'computedTest',
    data(){
        return {
            list:[
                {name:'张三',age:20,job:'it'},
                {name:'李四',age:21,job:'teacher'} 
            ],
            otherMsg:'xx村大学生就业统计'
        }
    },
    computed:{
        computedMsg(){
            return (item) => {
                return `${this.otherMsg}:${item.name}-${item.age}-${item.job}`
            }
        }
    },
    created(){
        setTimeout(() => {
            this.otherMsg = '2020下半年'
        }, 2000);
    }
}
</script>

// 1、执行evaluate/get 执行的内容的return,内部函数此时并不会执行,此时是怎么收集到依赖的?
// 答:因为闭包,其实是使用到了外部函数的this的。但没有收集到this.otherMsg
// 真正收集到这个依赖的,其实是在模板渲染的时候收集到的  {{ computedMsg(item) }},具体看上段模板渲染有说明
// 其中上面模板渲染的ubs的一项其实就是this.otherMsg的。也就是thisMsg的变化,为什么会触发视图更新呢,因为模板渲染中,执行了计算属性的内部函数,此时收集依赖到视图更新了

  • 总结:
  • 了解了计算属性,模板渲染过程中的依赖是怎么收集的
  • 深入分析了计算属性如果是函数调用,依赖收集又是怎么进行的
  • 建议:
1、如果能缓存一些计算值,建议用计算属性,把能缓存的计算,放在外部函数执行,内部函数执行其它的
2、如果不能缓存一些属性,有上可知,效率和绑定方法是一样的。建议用函数,会更明了