实现原理:
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)存在问题:
Vue 会加工、代理 data
为 _data
,通过浏览器控制台可以观察到,vm._data
的属性都是响应式数据(有对应的 getter、setter)
但是,如果我们后期自己给 Vue 实例添加属性,该属性没有经过 Vue 加工,所以不是响应式数据 (没有 getter、setter)
如果我们在 data 中放置一个数组数据,该数组会被代理,但数组内的元素不会被代理
就是说,只有修改数组本身,页面才会响应;而修改数组元素的话,页面不会响应
所以,通过 this.arr[index]
[删除]、[添加]、[修改] 数组的元素,数据会被修改,但页面不会重新渲染
<button @click="arr.length = arr.length - 1"> 删除数组元素 button>
<button @click="arr[arr.length] = 'z'"> 添加数组元素 button>
<button @click="arr[0] = '0'"> 修改数组元素 button>
<span v-for="item in arr"> {{item}} span>
let vm = new Vue({
el: "#app",
data: {
arr: ["s", "p", "m"]
}
});
可以使用能改变原数组的方法:
①push()
、②pop()
、③unshift()
、④shift()
、⑤splice()
、⑥sort()
、⑦reverse()
这 7 个方法都被 Vue 重写过,修改完数据后,能即时显示回页面
<button @click="arr.shift()"> 删除数组元素 button>
<button @click="arr.push('z')"> 添加数组元素 button>
<button @click="arr.splice(0, 1, '0')"> 修改数组元素 button>
可以发现,现在不只是数据发生了变化,页面也会重新渲染了
可以通过 filter
、map
等函数,对原数组变量进行重新赋值
<button @click="arr = arr.filter(ele => ele == 's')"> 过滤数组元素 button>
<span v-for="item in arr"> {{item}} span>
你可能认为这将重新渲染整个列表。事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用 实现了一些智能的启发式方法,所以用一个含有大量相同元素的数组去替换原来的数组是非常高效的操作
如果我在 data 里面放置了一个对象数据,该对象会被代理,对象里面的属性也会被代理。
就是说,不论我们修改对象本身,还是 [修改] 对象的属性,数据都是响应性的,会即时显示回页面
但是,如果我们为对象 [添加]、[删除] 属性,数据会被改变,但页面不会重新渲染
<button @click="obj.sex = 'male'"> 添加 sex 属性 button>
<button @click="delete obj.name"> 删除 name 属性 button>
<button @click="obj.age++"> 修改 age 属性值 button>
<span v-for="item in obj"> {{item}} span>
let vm = new Vue({
el: "#app",
data: {
obj: { name: "superman", age: 22 }
}
});
我们可以把整个对象数据替换掉,来显示更新后的对象数据
<button @click="change"> 修改整个对象 button>
<span v-for="item in obj"> {{item}} span>
let vm = new Vue({
el: "#app",
data: {
obj: { name: "superman", age: 22 }
},
methods: {
change() {
let newObj = {}; // 创建新对象
for (let [key, value] of Object.entries(this.obj)) { // 使用解构赋值
newObj[key] = value; // 复制原对象的属性
};
newObj.sex = "male"; // 添加属性 / 修改属性值
this.obj = newObj; // 对原对象重新赋值
}
}
});
我们知道 data
会被 Vue 代理为 _data
,在这之前,Vue 会先对 data
内的数据进行加工,使其成为响应性数据
如果我们直接在浏览器控制台给 Vue 实例添加属性,那这个后期添加上来的属性,因为没有被 Vue 加工,所以不是响应性数据
vm[._data].obj.gender = 'male'; // 无效写法
Vue.set(对象, "属性名", 属性值)
/vm.$set(对象, "属性名", 属性值)
- 用于设置 [属性] / [元素](响应性数据)
Vue.set(vm[._data].obj, 'gender', 'male'); // 牛逼写法
vm.$set(vm[._data].obj, 'gender', 'male'); // 也牛逼写法
这是 Vue 提供给我们的 API,使我们后期添加的数据,也经过 Vue 处理,成为响应性数据
<p v-for="(item, name, index) of obj"> {{index}} - {{name}} - {{item}} p>
<button @click="change"> 调用 set 修改对象数据 button>
let vm = new Vue({
el: "#app",
data: {
obj: { name: "superman", age: 21, }
},
methods: {
change() {
Vue.set(this.obj, "age", "22"); // 修改属性值
this.$set(this.obj, "sex", "male"); // 添加属性
}
}
});
vm.$set(数组, 下标, 元素值)
/ Vue.set(数组, 下标, 元素值)
Vue.set(vm[._data].arr, 0, 'new value');
vm.$set(vm[._data].arr, 0, 'new value');
注意:添加的对象不能是 [Vue 实例] (vm
) / [Vue 实例的根数据对象] (vm._data
)
就是说,不能通过这个方法给 Vue 添加 [响应式数据]
Vue.delete(对象, "属性名")
/vm.$delete(对象, "属性名")
- 用于删除 [属性] / [元素]
<template>
<div>
<p v-show="person.name">{{ person.name }}p>
<p>{{ person.age }}p>
<button @click="change_person">点击修改button>
div>
template>
<script>
import Vue from "vue"
export default {
name: "App",
data() {
return {
person: { name: "superman", age: 21 }
}
},
methods: {
change_person() {
Vue.delete(this.person, "name")
// this.$delete(this.person, "name")
}
},
};
script>
new Proxy(data, {
// 拦截 [获取] 属性
get(target, prop) {
return Reflect.get(target, prop) // 返回属性值
},
// 拦截 [添加]、[修改] 属性
set(target, prop, value) {
return Reflect.set(target, prop, value) // true-执行成功、false-执行失败
},
// 拦截 [删除] 属性
deleteProperty(target, prop) {
return Reflect.deleteProperty(target, prop) // true-删除成功、false-删除失败
}
})