VUE响应式原理、watch、计算属性computed
响应式原理
视图更新后做的事
视图内所有插值表达式和指令,都会重新运行和求值!
如果对数组进行修改并不会更新视图,但是数据改变了,
此时通过修改别的可以驱动视图更新的属性,视图更新使得数组的修改可以显示
实现响应式的条件(视图能更新):
- 数据要有劫持
- 视图上必须出现对应数据
(满足以上两个条件的数据 => watcher的依赖)
如何知道一个数据发生了变化?
js对象的属性都有4种描述
- 是否可以修改 writable:true
- 是否可以删除 configurable:true
- 是否可以枚举 enumerable:true 即 是否可以for in
- 值是什么 value
通过Object.getOwnPropertyDescriptor()
可以获得对象额属性的4种描述
let desc = Object.getOwnPropertyDescriptor(obj,'name')
通过Object.defineProperty()
来修改对象的属性的描述
Object.defineProperty(obj,'name',{
value:'asfa',
enumerable:false,
})
这里Vue源码内通过Object.defineProperty()来进行数据劫持(数据监听)。通过set和get方法
let val = data.msg;
Object.defineProperty(data,'msg',{
// 当msg被访问时候触发
get(){
console.log('msg被访问')
return val
},
// 当msg被修改时触发,可以在这里写dom操作,且
// 参数就是新修改的值
set(newVal){
val = newVal;
console.log('msg被修改了')
oDiv.innerText = newVal
}
})
如何在新增一个属性的同时给它添加数据劫持?
当一个obj没有那么属性时,当它实例化时,就没有办法给name进行数据劫持,在后续的对name修改,视图不会更新
用实例方法添加初始值和数据劫持,$是为了与用户的set(可能存在)进行区分
this.$set(this.obj,'name',1000)
用vue的静态方法添加
Vue.set(this.obj,'name',1000)
const vm = new Vue({
el: '#app',
data: {
obj:{}
},
methods: {
fn() {
// 新增属性name.(不算修改)
// this.obj.name = 1000;
// 用实例方法添加初始值和数据劫持
// this.$set(this.obj, 'name', 1000);
// 通过Vue的静态方法添加.
// Vue.set(this.obj, 'name', 1000);
// 访问实例的默认属性data.
// console.log(this.$data);
}
}
})
或者直接对已经被劫持的对象添加
this.obj.name = 1000
不行!
this.obj = { name : 1000}
可以!
计算属性和watch
计算属性是watch的一种特例.
计算属性能够实现的功能,watch都能实现.
watch能够实现的功能,*计算属性不一定能够实现*.例如异步操作.
计算属性内部不能包含异步操作.
但是计算机属性有缓冲,尽可能使用前一次的计算结果
这样是为了避免不必要的函数调用
可以手动关闭cache: false,
computed: {
num3: {
// 关闭计算属性的缓存功能.
// 这样做会导致每次视图更新,计算属性的get都会重复调用.返回计算属性的值.
cache: false,
get() {
console.log('num3计算属性函数触发了')
return this.num1 + this.num2
}
}
},
计算属性
计算属性的值,依赖别的数据的值的变化而变化
写法一:写成函数
当函数里面的值变化时都会触发改函数重新计算并返回最终的值
函数名就是我们需要使用的数据名
data: {
num1: Math.floor(Math.random() * 10) + 1,
num2: Math.floor(Math.random() * 10) + 1,
},
// 计算属性.可以简写成一个函数.return最终的值.
// 这个计算属性的函数,在num1或者num2变化时,都会自动触发返回新的值.
// 函数名就是我们需要使用的数据名.
computed: {
num3() {
console.log('num3函数触发了');
return this.num1 + this.num2
}
},
写法二:对象
通过属性的值为get()返回的值
如果要手动修改计算属性需要设置set()
data: {
num1: Math.floor(Math.random() * 10) + 1,
num2: Math.floor(Math.random() * 10) + 1,
},
// 计算属性的函数简写,实际上是全写的只有get的形式.
computed: {
num3: {
get() {
return this.num1 + this.num2
},
set(newVal) {
this.num1 = newVal - this.num2
}
}
},
watch
类似与监听属性变化,变化时候就触发,需要预先给定属性名,来接收变化
默认是没有立即执行的
data: {
num1: Math.floor(Math.random() * 10) + 1,
num2: Math.floor(Math.random() * 10) + 1,
num3: 0
},
// 侦测属性(监听)
watch: {
// 当num1变化时,就触发这个函数,
num1() {
this.num3 = this.num1 + this.num2;
},
// 当num2变化时,就触发这个函数
num2() {
this.num3 = this.num1 + this.num2;
}
},
如要立即执行
watch: {
num1: {
// 立即,默认触发一次handler.
immediate: true,
// handler在每次num1变化时都会触发.
handler() {
this.num3 = this.num1 + this.num2;
}
},
// 当num2变化时,就触发这个函数
num2: {
immediate: true,
// handler在每次num2变化时都会触发.
handler() {
this.num3 = this.num1 + this.num2;
}
}
},
异步:
watch: {
num1: {
immediate: true,
handler() {
setTimeout(() => {
this.num3 = this.num1 + this.num2
}, 3000);
}
}
},