计算属性原理
每有一个计算属性,就会生成一个computed watcher
- 当组件初始化的时候,computed 和 data 会分别建立各自的响应系统,Observer遍历 data 中每个属性设置 get/set 数据拦截,而computed则会遍历 computed 选项并对每一个计算属性实例化 watcher,并通过defineComputed 定义计算属性的 getter ,等待后边读取时触发
- 只不过执行构造函数时,lazy dirty 为 true,最后执行 watcher.value = undefined 并未执行 get 方法进行求值。
- 进入 mount 阶段,在执行 render 生成 vnode 时会读取到计算属性 text。此时全局的Dep.target是渲染watcher,它是用一个栈 targetStack维护的,此时栈是这样:[ 渲染watcher ],因为读取计算属性触发了getter,所以pushTarget(this),此时栈是这样的:[ 渲染watcher, computedWatcher ]
- computed 计算会读取 data,此时 data中的Dep 就收集到 computed-watcher,此时 data 的依赖收集器=【computed-watcher,页面watcher】
- 初始化计算watcher有一个属性dirty,这个是缓存的关键,一开始是true,直接进行计算求值并且this.value = this.get()。然后dirty置为false,直接返回value。
- 当某个属性发生变化,触发 set 拦截函数,然后调用自身消息订阅器 dep 的 notify 方法,遍历当前 dep 中保存着所有订阅者 wathcer 的 subs 数组,并逐个调用 watcher 的 update 方法,完成响应更新。
计算属性和method的区别
- 当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 立即执行一遍
}
}
- watch 在一开始初始化的时候,会 读取 一遍 监听的数据的值,于是,此时 那个数据就收集到 watch 的 watcher 了
- 然后 你给 watch 设置的 handler ,watch 会放入 watcher 的更新函数中
- 当 数据改变时,通知 watch 的 watcher 进行更新,于是 你设置的 handler 就被调用了
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
- 如果一个数据依赖于其他数据,那么把这个数据设计为computed的
- 如果你需要在某个数据变化时做一些事情,使用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去做改变。