补充:Vue监视数据的原理
案例:点击按钮修改数组中的某元素----存在数据改变, 但是Vue没有检测到变化的问题
<body>
<div id="root">
<h2>人员列表</h2>
<button @click='updateMei'>更新马冬梅的信息</button>
<ul>
<li v-for="(p,index) of persons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
按钮绑定事件,调用updateMei()
对第一个元素进行修改。
<script type="text/javascript">
new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'马冬梅',age:36,sex:'女'},
{id:'002',name:'周冬雨',age:25,sex:'女'},
{id:'003',name:'周杰伦',age:45,sex:'男'},
{id:'004',name:'温兆伦',age:18,sex:'男'}
],
},
methods:{
updateMei(){
//奏效
this.persons[0].name = '马老师'
this.persons[0].age = 50
this.persons[0].sex = '男'
}
}
})
</script>
点击按钮后:
此时修改成功,但是如果写在一条语句中:
this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'}
在控制台输出persons
,可以看到数据其实改了, 但是Vue没有检测到变化。
1 探寻Vue监测对象的原理
由数据代理可知,data被赋给了Vue实例的_data,但是打开控制台查看vm._data发现与原data不同的是,配置项不是直接给的,而是由getter给的,修改也是由setter完成的。
<body>
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<ul>
<li v-for="(p,index) of persons" :key="p.id">{{p.name}}-{{p.age}}-{{p.sex}}</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
name:'武大',
address:'武汉'
}
})
</script>
也就是每一组key: value都形成了getter、setter的写法。
2 数据改变引起页面改变的流程
3 模拟数据监测(代码中不引入Vue)
3.1 错误写法
<script type="text/javascript">
let data = {
name:'武大',
address:'武汉'
}
Object.defineProperty(data,'name',{
get(){
return data.name
},
set(val){
data.name = val
}
})
</script>
在控制台输出data,结果报"溢出"如下:
读取的流程:
修改name报错(超出最大调用堆栈大小)如下:
修改的流程:
3.2 实际的大概写法
<script type="text/javascript">
let data = {
name:'武大',
address:'武汉'
}
//拿到data之后,先创造一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
let vm = {}
vm._data = data = obs
//Observer是构造函数,参数为对象
function Observer(obj){
//1.汇总对象中所有属性形成一个数组
const keys = Object.keys(obj)
//2.遍历对象中所有的键,并添加属性
keys.forEach((k)=>{
Object.defineProperty(this,k,{
get(){
return obj[k]//返回传入对象对应的属性值
},
set(val){ //收到修改的值
obj[k] = val //把传入对象的k所对应的值改掉
},
})
})
}
</script>
与真实不同(这段代码不够完善)的是:
描述 | 这段代码 | 真实Vue |
---|---|---|
修改数据 | vm._data.name | vm.data |
data对象里有对象 | 对象里的属性没有getter、setter | 有递归来实现每一个属性都有 |
data里数组的属性 | 没有getter、setter | 有getter、setter |
Object.keys(obj):获取对象中所有可枚举的键,并返回一个数组。适用于需要遍历随想键的场景。
补充:Object常见API Object.defineProperty回顾
4 补充API:Vue.set( )
4.1 给data中的对象添加属性配置
容器部分:
<body>
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr/>
<button @click='addsex'>添加性别属性</button>
<h2>学生姓名:{{student.name}}</h2>
<h2>学生性别:{{student.sex}}</h2>
<h2>学生年龄:真实{{student.age.rage}},对外:{{student.age.sage}}</h2>
<h2>朋友们:</h2>
<ul>
<li v-for="(f,index) of student.friends" :key="index">{{f.name}}-{{f.age}}</li>
</ul>
</div>
</body>
Vue实例:
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
name:'武大',
address:'武汉',
student:{
name:'fx',
//sex:'女',
//注释后不会报错但是不显示,因为没有的话是undefined,但是vue不会显示undefined到页面上
//即{{student.sex}}不报错,{{sex}}会报错
age:{
rage: 24,
sage: 18,
},
friends:[
{name:'a',age:26},
{name:'b',age:25}
]
},
},
methods:{
adddsex(){
//第一种:利用Vue的API
Vue.set(this.student,'sex','女')
//Vue.set(对象名,属性,值)
//官网:Vue.set(target,propertyName/index,value)
//第二种:Vue实例的自带方法
this.$set(this.student,'sex','女')
}
}
})
</script>
注意:只能给Vue实例的data中的某个对象添加属性,而不能直接给Vue实例或者data直接添加
5 探寻Vue监测数组的原理
案例
<body>
<div id="root">
<h2>朋友们:</h2>
<ul>
<li v-for="(f,index) of student.friends" :key="index">{{f.name}}-{{f.age}}</li>
</ul>
<h2>爱好:</h2>
<ul>
<li v-for="(h,index) of student.hobby" :key="index">{{h}}</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
student:{
friends:[
{name:'a',age:26},
{name:'b',age:25}
],
hobby:['抽烟','喝酒','烫头']
}
}
})
</script>
控制台输出该实例:
此时直接通过索引值修改值是无法监测到的,也就是之前的那个问题
程序员通常会使用JS数组的一些常用方法,其中修改原数组的几个方法可以被Vue监测到:
方法 | 描述 |
---|---|
push() | 最后面添加元素,返回新数组长度 |
unshift() | 最前面添加元素,返回新数组长度 |
pop() | 删除最后一个元素,返回删除的元素 |
shift() | 删除最前面的元素,返回删除的元素 |
splice()方法 | (开始删除元素的索引,删除元素总数,插入的元素)元素插入的位置就是删除元素开始的位置 |
sort() | 将元素排序,并返回排序好的数组 降序:sort(function(a,b){return a-b}) 升序:sort(function(a,b){return b-a}) |
reverse() | 将元素进行倒叙,返回倒叙的数组 |
之前那个问题也就要写成:
this.person.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'})
Vue能监测到数组的改变,其实是包装了数组这些常用方法。
对于普通数组来说,比如这里arr调用了push( ),这个方法调的是Array原型对象上的push方法(Array.prototype.push)。
let arr = [1,3,5,7,9]
arr.push(10)
arr.push === Array.prototype.push
//返回true
而Vue实例中data的student配置对象的hobby数组调用的已经不是Array原型对象的方法了(Array.prototype.push)
vm._data.student.hobby.push === Array.prototype.push
//返回false
此时调用的push其实是Vue写的push,也就是说沿着hobby的原型找的是Vue写的push。
Vue写的push:
- 调用Array.prototype.push
- 重新解析模板–>生成虚拟DOM–>新旧DOM比较–>更新页面
所以也就能监测到这些方法的调用,监测到数组的改变。
除了这些数组方法,使用Vue.set,也可以改变数组元素。
//将原来第二个元素的喝酒变成打台球
//第一种写法
Vue.set(vm.student.hooby,1,'打台球')
//第二种写法
vm.$set(vm.student.hooby,1,'打台球')
6 总结Vue监视数据
<body>
<div id="root">
<h1>学生信息:</h1>
<button @click='student.age++'>年龄+1</button><br/>
<button @click='addsex'>添加性别属性</button><br/>
<button @click='addfriend'>在首位添加一位朋友</button><br/>
<button @click='updatefirstfriendname'>修改第一个朋友名字</button><br/>
<button @click='addhobby'>添加一个爱好</button><br/>
<button @click='updatefirsthobby'>修改第一个爱好为开车</button><br/>
<button @click='removehooby'>过滤爱好</button><br/>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h 3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h2>朋友们:</h2>
<ul>
<li v-for="(f,index) of student.friends" :key="index">{{f.name}}-{{f.age}}</li>
</ul>
<h2>爱好:</h2>
<ul>
<li v-for="(h,index) of student.hobby" :key="index">{{h}}</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
student:{
name:'fx',
age:18,
friends:[
{name:'a',age:26},
{name:'b',age:25}
],
hobby:['抽烟','喝酒','烫头']
}
},
methods:{
addsex(){
Vue.set(this.student,'sex','女')
//或
this.$set(this.student,'sex','女')
},
addfirend(){
this.student.friends.unshift({name:'crisp',age:25})
//添加的数组里的对象是响应式的,即有getter和setter
},
updatefirstfriendname(){
this.student.friends[0].name = '张三'
//this.student.friends[0]为对象,这里是修改对象的属性,有对应的getter和setter
//若直接修改数组元素 this.student.friends[0] = '张三', Vue是不承认的
},
addhobby(){
this.student.hobby.push('学习')
},
updatefirsthobby(){
this.student.hobby.splice(0,1,'开车')
//或
Vue.set(this.student.hobby,0,'开车')
//或
this.$set(this.student.hobby,0,'开车')
},
removehooby(){
this.student.hobby = this.student.hobby.filter((h)=>{
return h !== '抽烟'
})
}//也是要变更数组,但是不会变更原始数组
}
})
</script>
- vue会监视data中所有层次的数据
- 对象的数据监测:通过setter监视,且要在new Vue时就传入要检测的数据(指一开始就写好)
(1)后追加的属性,默认不做响应式处理
(2)如需将添加的做响应式处理,需用Vue.set的API - 数据的数据监测:通过包裹数组更新元素的方法
- 修改数组元素的方法:
(1)使用数组方法API
(2)Vue.set( )或 vm.$set( )
==注意==:Vue.set( )和 vm.$set( )不能用于为 Vue实例 和 实例的根数据对象data 添加属性!!
7 数据劫持
将代码中的data改成了控制台输出实例.data,即遍历了所有的属性并变成getter和setter