文章目录
Vue监测数据的原理
一、先来看一个问题
如果我要点击按钮实现更新冯万宁儿的信息,那么如果一个属性一个属性地改,可以修改成功,并且Vue也会检测到并更新到页面。但是我如果直接把整个对象改了,可以修改成功,但是Vue监测不到并且不更新到页面,这是为啥捏?请君慢慢往下阅读
<!-- 准备好一个容器 -->
<div id="root">
<h1>人员列表</h1>
<button @click="updateNing">更新冯万宁儿</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}}----{{p.age}}----{{p.sex}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
persons: [
{ id: 001, name: '冯万宁儿', age: 23, sex: '男' },
{ id: 002, name: '屁及万儿', age: 18, sex: '男' },
{ id: 003, name: '及丽热巴', age: 10, sex: '女' },
{ id: 004, name: '冯小刚儿', age: 60, sex: '男' }
],
},
methods: {
updateNing() {
// this.persons[0].name = '冯千宁儿'; //奏效
// this.persons[0].age = 66; //奏效
// this.persons[0].sex = '女'; //奏效
this.persons[0] = { id: 001, name: '冯千宁儿', age: 66, sex: '女' };
//上面这么写,也奏效,数据实际上已经改了,但是Vue监测不到所以没更新到页面,为啥捏?
}
}
})
</script>
二、原生js模拟Vue监视data对象中的数据
用原生js写一个Vue监视data数据改变的效果
代码自己琢磨琢磨,有点儿意思。可以看出,Vue监视数据的原理,就是靠setter
如果不理解obj[k]
,想想前边讲Object.defineProperty时那个number
let data = {
name: 'zzy',
age: 18
}
//创建一个监视的实例对象,用来监视data中数据的变化
const obs = new Observer(data);
const vm = {};
vm._data = obs;
//创建一个类似vm._data的构造函数
function Observer(obj) {
//1.创建一个数组接收传入对象的属性名
let arr = Object.keys(obj); //['name','age']
//2.遍历属性名,让Observer实例对data中的每个数据进行数据代理
arr.forEach((k) => {
Object.defineProperty(this, k, {
get() {
//有人想读实例中的属性值,我就把data中对应的属性值拿过来
return obj[k];
},
set(val) {
//有人想改实例中的属性值,我就把data中对应的属性值更改(数据代理)
console.log(`${k}被改了,我要重新解析模板,生成虚拟DOM,开始diff算法`);
obj[k] = val;
}
})
})
}
这只是个模拟,实际上Vue做了更多事情,比如:
1、在setter中不是简单的输出,而是重新解析模板,生成虚拟DOM,新旧虚拟DOM对比,更新页面实现响应式。
2、Vue中vm还对_data做了数据代理,我可以直接vm.name,不用vm.__data.name
3、我这么写,如果数据是套娃的(对象中还有对象还有对象),就监测不到。但是Vue就可以,不管你数据藏得多深,Vue都能找到,每个数据都有自己的getter和setter。只要有数据被改,就会实现响应式。(这里面好像涉及了深拷贝的知识?回头补补)
三、Vue监测对象中的数据
1.怎么监视对象中的数据?
1、vue会监视data中所有层次的数据,不管你藏得有多深。
2、如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value)
2.Vue.set()
的使用
用法:
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新property,因为 Vue 无法探测普通的新增 property (比如 this.girlfriend.sex = '女'
)为啥呢?
_data在收集data中的数据之前,先做了一个加工(这个加工也可以称之为数据劫持
)那就是给每个数据匹配一个getter和setter,前面说了,Vue监视数据的原理,就是靠setter
。所以如果我们后期手动直接给data添加属性(注意区别手动添加和从data中收集),是无法实现响应式的,因为没办法给它setter,只有从data中收集过来的属性才能通过一个鱿鱼西定义的好像是叫Observer什么什么的方法给它搞个setter。
Vue.set()
是有局限性的!!!它只能给data中的某个对象追加属性,不能给vm 或 vm的根数据对象(data或_data) 添加属性!!!
也就是说第一个参数不能是 Vue 实例,或者 Vue 实例的根数据对象(__data)
本小节用到的案例:
<div id="root">
<h2>我的名字:{{name}}</h2>
<h2>我的年龄:{{age}}</h2>
<h3 v-if="sex">我的性别:{{sex}}</h3>
<button @click="addmySex">点击添加我的性别</button>
<hr>
<h2>她的名字:{{girlfriend.name}}</h2>
<button @click="addherSex">点击添加性别,属性值为女</button>
<h2 v-if="girlfriend.sex">她的性别:{{girlfriend.sex}}</h2> <!-- undefined不显示,也不报错 -->
<h2>她的年龄:对外{{girlfriend.age.fakeAge}},真实{{girlfriend.age.realAge}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="p in girlfriend.friends" :key="p.id">
{{p.name}}----{{p.age}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
name: 'zzy',
age: 18,
girlfriend: {
name: 'ht',
// sex: '女',
age: {
realAge: 23,
fakeAge: 18
}
}
},
methods: {
addherSex() {
// Vue.set(this.girlfriend, 'sex', '女');
this.$set(this.girlfriend, 'sex', '女'); //vm.$set(vm.girlfriend, 'sex', '女');
},
addmySex() {
Vue.set(this, 'sex', '男');
}
},
})
</script>
四、Vue监测数组中的数据
1.如何监测数组中的数据?
其实是Vue里面定义了一个非常牛逼的方法,目的只有一个:实现响应式。
方法里包装的代码本质就是做了两件事:
(1)调用原生对应的方法对数组进行更新。然后第二步在这个基础上再加点料
(2)重新解析模板,生成虚拟DOM,diff算法……进而更新页面,实现响应式。
所以在Vue修改数组中的某个元素实现响应式一定要用如下方法:
(1)使用这些API:push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
(2)Vue.set()
或 vm.$set()
也可以实现响应式,第二个参数写索引,第三个写元素,可以改也可以加
2.案例
<div id="root">
<h2>我的名字:{{name}}</h2>
<h2>我的年龄:{{age}}</h2>
<hr>
<h2>她的名字:{{girlfriend.name}}</h2>
<button @click="addHobby">点击替换'跳'的爱好</button>
<h2>爱好</h2>
<ul>
<li v-for="(h,index) in girlfriend.hobby" :key="index">
{{h}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
name: 'zzy',
age: 18,
girlfriend: {
name: 'ht',
hobby: ['唱', '跳', 'rap']
}
},
methods: {
addHobby() {
//除了那7个方法外,set方法也可以改变数组实现响应式
Vue.set(this.girlfriend.hobby, 1, '打游戏');
}
},
})
</script>
五、回头看看第一个问题
说了这么多,再回头看第一个问题,就恍然大悟了
1、单独改每个属性可以奏效,是因为Vue可以监测到对象里的每个属性,不管它藏的多深,只要是个对象,就有相应的getter和setter
2、将数组中的元素单独替换,数据修改成功,但是Vue没有监测到,页面不显示,这是因为数组中的每个元素是没有getter和setter的,如果修改数组中的元素无法实现响应式.
3、要想让更改数组中的元素实现响应式,就得调用数组的七个api
或者用Vue.set()
方法
<!-- 准备好一个容器 -->
<div id="root">
<h1>人员列表</h1>
<button @click="updateNing">更新冯万宁儿</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}}----{{p.age}}----{{p.sex}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
persons: [
{ id: 001, name: '冯万宁儿', age: 23, sex: '男' },
{ id: 002, name: '屁及万儿', age: 18, sex: '男' },
{ id: 003, name: '及丽热巴', age: 10, sex: '女' },
{ id: 004, name: '冯小刚儿', age: 60, sex: '男' }
],
},
methods: {
updateNing() {
// this.persons[0].name = '冯千宁儿'; //奏效
// this.persons[0].age = 66; //奏效
// this.persons[0].sex = '女'; //奏效
// this.persons[0] = { id: 001, name: '冯千宁儿', age: 66, sex: '女' };
//上面这么写,也奏效,数据实际上已经改了,但是Vue监测不到所以没更新到页面,为啥捏?
//实际上是因为Vue监测数组时,数组没有相应的setter和getter,直接改Vue监测不到
//但是数组中的每个对象中的属性都有自己的getter,setter,所以单独改属性时可以监测到的
//要想让更改数组中的元素实现响应式,就得调用数组的七个api或者用set
this.persons.splice(0, 1, { id: 001, name: '冯千宁儿', age: 66, sex: '女' });
}
}
})
</script>
六、来个综合大练习
<div id="root">
<button @click="addSex">添加一个性别属性,默认为女</button>
<button @click="addHeight">添加一个身高属性,默认为170</button><br>
<button @click="girlfriend.age.realAge--">年龄-1</button>
<button @click="addFriend">在列表前加一个朋友</button><br>
<button @click="updateFriend">修改第一个朋友的名字为张三</button>
<button @click="addHobby">添加一个爱好</button><br>
<button @click="updateHobby">修改第一个爱好为散步</button>
<button @click="removeHobby">过滤掉爱好中的跳</button>
<h2>名字:{{girlfriend.name}}</h2>
<h2>年龄:对外{{girlfriend.age.fakeAge}},真实{{girlfriend.age.realAge}}</h2>
<h2 v-if="girlfriend.sex">性别:{{girlfriend.sex}}</h2>
<h2 v-if="girlfriend.height">身高:{{girlfriend.height}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="p in girlfriend.friends" :key="p.id">
{{p.name}}----{{p.age}}
</li>
</ul>
<h2>爱好</h2>
<ul>
<li v-for="(h,index) in girlfriend.hobby" :key="index">
{{h}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
name: 'zzy',
age: 18,
girlfriend: {
name: 'ht',
// sex: '女',
age: {
realAge: 23,
fakeAge: 18
},
friends: [
{ id: 001, name: 'jack', age: 10 },
{ id: 002, name: 'rose', age: 8 },
],
hobby: ['唱', '跳', 'rap']
}
},
methods: {
addSex() {
Vue.set(this.girlfriend, 'sex', '女');
},
addHeight() {
this.$set(this.girlfriend, 'height', 170);
},
addFriend() {
this.girlfriend.friends.unshift({ id: 003, name: 'alice', age: 5 }); //有效写法
},
updateFriend() {
this.girlfriend.friends[0].name = '张三';
},
addHobby() {
this.girlfriend.hobby.push('打游戏');
},
updateHobby() {
// this.girlfriend.hobby[0] = '散步'; //无效写法
// this.girlfriend.hobby.splice(0, 1, '散步'); //有效写法
Vue.set(this.girlfriend.hobby, 0, '散步'); //有效写法
},
removeHobby() {
// 变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法
// 例如 filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。
// 当使用非变更方法时,可以用新数组替换旧数组:
this.girlfriend.hobby = this.girlfriend.hobby.filter((ele) => {
return ele !== '跳';
})
}
},
})
</script>
七、总结
Vue监视数据的原理
1.vue会监视data中协有层次的数据。
2.如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm. $set(target, propertyName/index, value)3.如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新。
(2).重新解析模板,进而更新页面。4.在Vue修改数组中的某个元素一定要用如下方法:
1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2.Vue.set()或vm.$set()特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根据对象 添加属性!!!