一、数据代理
1.什么是数据代理?
数据代理:通过一个对象代理对另一个对象中属性的操作
2.Vue数据代理
通过vm对象来代理配置对象data中所有属性的操作。 (如果想访问data里的数据,可以直接通过实例对象,如vm.name,vm.age拿到具体的数值,之所以能这样做,就是因为Vue实现了数据代理)
3.为什么要用数据代理
更方便的读取和修改data中的属性
4.为什么要先将数据收集到_data中,再代理出去
为了更高效地实现数据监测
5.实现原理
①Vue将配置对象data进行了一次加工 ,然后将加工后的数据收集到vm._data中,实例对象通过vm._data的方式可以拿到加工后的data。但是属性的值不再直接给出,而是通过响应式getter来获取,当data中数据改变的时候,就会调用响应式setter,导致重新解析模板,然后生成新的虚拟DOM进行新旧DOM对比,最后更新页面。
②然后通过Object.defineProperty() 让vm上拥有vm._data中的所有属性,即配置对象data中的所有属性,从而实现了数据代理。
模拟数据绑定到vm._data的过程:
<script type="text/javascript">
let data={
name:"gyc",
age:18
}
const obs=new Observer(data)
console.log(obs)
let vm={}
vm._data=data=obs
function Observer(obj){
const keyArr=Object.keys(obj)
keyArr.forEach((key)=>{
Object.defineProperty(this,key,{
get(){
return obj[key]
},
set(val){
obj[key]=val
}
})
})
}
</script>
注意:_data里面不是数据代理,而是做了一个数据劫持。Vue去监测_data中的数据变化,从而达到界面也跟着变化。 添加到vm上的属性才是数据代理
二、数据监测
1.监测对象
首先写出需要用到的静态页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新数据</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr/>
<h2>学生姓名:{{student.name}}</h2>
<h2>学生性别:{{student.sex}}</h2>
<h2>学生年龄:{{student.age}}</h2>
<h1>朋友</h1>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}} - {{f.age}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data:{
name:'JLU',
address:'jilin',
student:{
name: 'gyc',
age: 20,
friends: [
{name: 'jack', age: 21},
{name: 'mary', age: 20}
]
},
}
})
</script>
</body>
</html>
需要注意的是,模板中的{{sex}}并没有出现在data中。当输出一个对象中不存在的属性值时,会输出undifined,但经过Vue处理后,不会将undifined显示到页面上,而是什么都不显示,控制台也不报错。
现在的需求是,添加一个按钮,点击按钮,为学生添加性别属性。要求不能改变data中现有的属性,即sex不能直接添加到data中。
前面提到过_data,考虑将sex添加到_data中,浅试一下: 页面效果: 可以发现,添加的sex属性并没有渲染到页面上。 查看vm._data: 可以看到通过这种方式添加属性,_data中添加了sex,但是没有生成相应的setter和getter,这说明后添加的属性不是响应式属性,无法渲染到页面上。
2.Vue.set监测对象
为了解决上面的问题,为后添加的属性设置响应式,需要使用Vue提供的API: ? Vue.set(target,propertyName/index,value) ? vm.$ser(target,propertyName/index,value)
通过Vue.set()试一下是否可以实现功能: 页面效果: 发现通过这种方式可以将新添加的属性渲染到页面上。 在实现功能后我们可以考虑一下优化简写,由于数据代理,vm.student === vm._data.student,所以可以简写为Vue.set(vm.student,‘sex’,‘女’)。
完整的实现上面提出的需求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新数据</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr/>
<h2>学生姓名:{{student.name}}</h2>
<button @click="addSex">点击添加性别</button>
<h2 v-show="student.sex">学生性别:{{student.sex}}</h2>
<h2>学生年龄:{{student.age}}</h2>
<h1>朋友</h1>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}} - {{f.age}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data:{
name:'JLU',
address:'jilin',
student:{
name: 'gyc',
age: 20,
friends: [
{name: 'tom', age: 19},
{name: 'richard', age: 20}
]
},
},
methods:{
addSex(){
Vue.set(this.student,'sex','女')
}
}
})
</script>
</body>
</html>
但是使用这样的方法有一定的局限性,上面的例子是给data中的student对象添加新的属性,假如直接给data对象添加属性呢? 比如假设需要在这里添加一个新的属性建校事件time,如果仍然采用Vue.set方法的话,就会报错。 注意:Vue.set只适用于给data中的对象添加新的属性(例如:data.student),而不能直接给data添加新的属性。
3.监测数组
先给出静态页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新数据</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<button @click="addSex">点击按钮新增性别</button>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr/>
<h2>学生姓名:{{student.name}}</h2>
<h2>学生性别:{{student.sex}}</h2>
<h2>学生年龄:{{student.age}}</h2>
<h1>朋友</h1>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}} - {{f.age}}
</li>
</ul>
<h1>爱好</h1>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data:{
name:'JLU',
address:'jilin',
student:{
name: 'gyc',
age: 20,
friends: [
{name: 'tom', age: 19},
{name: 'richard', age: 20}
],
hobby:['吃饭','睡觉','打豆豆']
},
},
methods:{
addSex(){
this.$set(this.student, 'sex', '男')
}
}
})
</script>
</body>
</html>
其中student.hobby是数组 此时页面效果为: 在控制台中打印vm._data后我们可以清晰的看到,对于student中的hobby数组,数组整体来说有get和set,是响应式的。但是数组里面的元素并非响应式,而是简单的挂在了数组中。 现提出一个需求,将hobby数组中的第一项 吃饭 改成 学习。 如果通过索引来改变数组中的值,将hobby[0]改为学习: vm._data中已经修改了,但是没有渲染到页面上,页面没反应: 在Vue中,如果想要通过索引修改数组中的数值,就需要使用某些特殊方法。
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push() pop() shift() unshift() splice() sort() reverse()
这些方法都会引起数组本身的变化,称为变更方法 。
因此,想要实现上面提出的需求,可以通过以下两种方法实现: ①通过splice()方法
vm.student.hobby.splice(0,1,'学习')
②通过Vue.set()
Vue.set(vm.student.hobby,0,'学习')
注意这里Vue.set()的第二个参数是数组中元素的下标。
对于一些数组的非变更方法 ,例如filter()、concat() 和 slice(),并不会引起数组本身的变化,若想要调用这些方法实现响应式,可以用新数组替换旧数组。例如:
this.student.hobby=this.student.hobby.filter((item)=>{
return item!='吃饭'
})
4.Vue监测总结
1.vue会监视data中所有层次的数据
2.如何监测对象中的数据?
? 通过setter事件监视,且要在new Vue时就传入要监测的数据
? (1).在对象后追加的属性,Vue默认不做响应式处理
? (2).如需给后添加的属性做响应式,需要使用以下API:
? Vue.set(target,propertyName/index,value)
? vm.$ser(target,propertyName/index,value)
3.如何监测数组中的数据?
? 通过包裹数组更新元素的方法实现,本质就是做了两件事:
? (1).调用原生对应的方法对数组进行更新
? (2).重新解析模板,进而更新页面
4.在Vue修改数组中的某个元素需要用到以下方法:
? 1.使用这些API:push(),pop(),shift(),unshift(),splice(),sort(),reverse()
? 2.Vue.set()或vm.$set
|