Vue Computed和watch

161 阅读3分钟
计算属性原理

每有一个计算属性,就会生成一个computed watcher

  1. 当组件初始化的时候,computed 和 data 会分别建立各自的响应系统,Observer遍历 data 中每个属性设置 get/set 数据拦截,而computed则会遍历 computed 选项并对每一个计算属性实例化 watcher,并通过defineComputed 定义计算属性的 getter ,等待后边读取时触发
  2. 只不过执行构造函数时,lazy dirty 为 true,最后执行 watcher.value = undefined 并未执行 get 方法进行求值。
  3. 进入 mount 阶段,在执行 render 生成 vnode 时会读取到计算属性 text。此时全局的Dep.target是渲染watcher,它是用一个栈 targetStack维护的,此时栈是这样:[ 渲染watcher ],因为读取计算属性触发了getter,所以pushTarget(this),此时栈是这样的:[ 渲染watcher, computedWatcher ]
  4. computed 计算会读取 data,此时 data中的Dep 就收集到 computed-watcher,此时 data 的依赖收集器=【computed-watcher,页面watcher】
  5. 初始化计算watcher有一个属性dirty,这个是缓存的关键,一开始是true,直接进行计算求值并且this.value = this.get()。然后dirty置为false,直接返回value。
  6. 当某个属性发生变化,触发 set 拦截函数,然后调用自身消息订阅器 dep 的 notify 方法,遍历当前 dep 中保存着所有订阅者 wathcer 的 subs 数组,并逐个调用 watcher 的 update 方法,完成响应更新。
计算属性和method的区别
  1. 当data不变时,多次调用计算属性,只计算一次。多次调用method会调用多次
function pushTarget(target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
watch

简单使用:

 watch:{
     a(val, oldVal){//普通的watch监听
         console.log("a: "+val, oldVal);
     },
     b:{//深度监听,可监听到对象、数组的变化
         handler(val, oldVal){
             console.log("b.c: "+val.c, oldVal.c);
         },
         deep:true //true 深度监听
     },
     c:{//立即执行一遍而不需要等到有变化才执行
         handler(val, oldVal){
             console.log("b.c: "+val.c, oldVal.c);
         },
         immediate:true //true 立即执行一遍
     }
 }
  1. watch 在一开始初始化的时候,会 读取 一遍 监听的数据的值,于是,此时 那个数据就收集到 watch 的 watcher 了
  2. 然后 你给 watch 设置的 handler ,watch 会放入 watcher 的更新函数中
  3. 当 数据改变时,通知 watch 的 watcher 进行更新,于是 你设置的 handler 就被调用了

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

  1. 如果一个数据依赖于其他数据,那么把这个数据设计为computed的
  2. 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化
watch: {
    message1 (newValue) {
      console.log(newValue);
    }
  },
created () {
    this.message1 = 1
    this.message1 = 2
    this.message1 = 3
  },

此时watch回调函数只执行一次log为3,原因:(详细查看6.7.1)

将 watcher 压入队列,重复的 watcher 知会被压入一次,这样在一个事件循环中 触发了多次的 watcher 只会被压入队列一次。如例子中异步队列中只有一个 渲染 watcher 和一个computed watcher;

如果想每次都执行,需要如下:

watch: {
    message1: {
      sync: true,
      handler (newValue) {
        console.log(newValue);
      }
    }
}
面试题
<template>
    <div>
        <p>valueText:{{ valueText }}</p>
        <p>xxx:{{ xxx }}</p>
        <p>abc:{{ abc }}</p>
        <button @click="changeValue">改变 abc 和 xxx 的值</button>
    </div>
</template>

<script>
    export default {
        data () {
            return {
                xxx: false
            }
        },

        computed: {
            valueText () {
                return this.abc && this.xxx;
            }
        },

        created () {
            this.abc = false;
        },

        methods:{
            changeValue () {
                this.abc = true;
                this.xxx = true;
            }
        }
    }
</script>

因为&& 符号和abc初始值为false,所以initComputed的时候valueText的watcher只有abc能收集到,能收集的前提是abc在data里被定义了成响应式,这样点击触发abc的改变也会通知到valueText去做改变。