文章目录
引例,更新问题
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<button @click="updataZs">更新张三信息</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">{{p.name}}-{{p.age}}</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
name: "yang",
persons: [
{ id: "001", name: "张三", age: 18 },
{ id: "002", name: "李四", age: 21 },
{ id: "003", name: "王五", age: 19 },
]
},
methods: {
updataZs(){
// this.persons[0].name="yang"//奏效
// this.persons[0].age="18"//奏效
this.persons[0] = {id: "001", name: "yang", age: 18}
}
},
});
</script>
</body>
</html>
点击更新按钮之后,vm中的数据发生了改变但是页面数据没有改变。
vue监测数据改变原理
我们在页面模板中使用data中的数据时,data中的数据会在页面显示,并且当data中的数据发生改变时页面中相应的数据也发生改变。
那么vue是如何监视data中的数据改变的呢?
——通过设置响应式函数,当数据一发生改变,就调用响应式函数,响应式函数会引起模板的重新解析,将改变后的数据显示到页面上,那么响应式函数是如何产生的呢,通过data的数据加工。
data加工
我们知道 vm对象和data之间形成数据代理,即vm对象(vue对象)代理data对象中属性的操作。
代理过程如下:
实际上源码中的data和vm对象中的_data不是完全一样的,vm._data中有每个属性的getter和setter方法,而data中只有属性值。这是因为源码data在存储在vm中之前先进行了数据加工,添加了getter和setter方法,之后在存储在vm中。
而添加的getter和setter方法是响应式的也就是说,当data中的数据一发生改变,就会调用响应式的setter方法,响应式setter方法会引起模板的重新解析。
vue监测数据改变原理的简单实现
vue通过响应式setter方法监测数据改变,这里进行简单的解析实现。
vue检测对象改变的简单实现
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<!-- <script type="text/javascript" src="../js/vue.js"></script> -->
</head>
<body>
<script type="text/javascript">
let data ={
name:"yang",
age:18
}
// 创建检测实例的对象,检测数据的变化
const obs = new Observer(data)
// 准备vm实例对象
const vm = {}
vm._data = data = obs
function Observer(obj){
// 获取对象中的所有属性
const keys = Object.keys(obj);
// 遍历
keys.forEach((k)=>{
Object.defineProperty(this,k,{
get(){
return obj[k]
},
set(val){
console.log(`${k}被修改了,重新解析模板`)
obj[k] = val
}
})
})
}
</script>
</body>
</html>
问题:vue对于后添加的属性不会设置setter和getter
eg:
_<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<!-- 如果一个对象的某属性未定义,返回undefined,vue对于undefined 类型的数据不显示,也不报错 -->
<h2>朋友:{{friends.id}}-{{friends.name}}-{{friends.age}}-{{friends.sex}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
name: "yang",
age:"18",
friends: { id: "001", name: "张三", age: 18 },
},
});
</script>
</body>
</html>
解决
-
法一:使用vue提供的api
Vue.set(要添加的对象,要添加的属性,要添加的属性值)
上述命令也可以使用vm代理数据data即:
Vue.set(vm.friends,'sex','女')
-
法二:使用vm提供的api
vm.$set(要添加的对象,要添加的属性,要添加的属性值)
上述命令也可以使用vm代理数据data即:
vm.$set(vm.friends,'sex','女')
代码实现:
_<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<!-- 如果一个对象的某属性未定义,返回undefined,vue对于undefined 类型的数据不显示,也不报错 -->
<h2>朋友:{{friends.id}}-{{friends.name}}-{{friends.age}}-{{friends.sex}}</h2>
<button @click="addSex">点击添加朋友性别</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
name: "yang",
age:"18",
friends: { id: "001", name: "张三", age: 18 },
},
methods: {
addSex(){
// Vue.set(this.friends,'sex','男')
this.$set(this.friends,'sex','男')
}
},
});
</script>
</body>
</html>
注意: 该方法只能给data中的对象添加属性,而不能给data添加属性。
vue检测数组改变的简单实现
问题: data中数组中的数据的更改无法使页面重新解析
data中数组中的数据是没有设置gettersetter方法的,所以如果data中数组数组中的数据发生改变是无法重新解析界面的。
eg:
_<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<!-- 如果一个对象的某属性未定义,返回undefined,vue对于undefined 类型的数据不显示,也不报错 -->
<h2>爱好:</h2>
<ul>
<li v-for="(hobby,index) in hobbies" :key="index">
{{hobby}}
</li>
</ul>
<h2>朋友:</h2>
<ul>
<li v-for="(friend,index) in friends" :key="index">
{{friend.id}}-{{friend.name}}-{{friend.age}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
name: "yang",
age: "18",
hobbies:["吃饭","睡觉","打豆豆"],
friends: [
{ id: "001", name: "张三", age: 18 },
{ id: "002", name: "李四", age: 19 },
{ id: "003", name: "王五", age: 20 },
{ id: "004", name: "赵六", age: 21 },
],
},
methods: {},
});
</script>
</body>
</html>
解决1:
虽然不能通过直接更改数组元素使得页面重新解析加载,但是vue将数组的变更方法
进行了包裹,所以只要我们使用这些变更方法
来修改数组,修改完成之后就会触发视图更新。
变更方法包括:.push(). pop().shift().unshift(). splice(). sort(). reverse()
EG:
vue将包裹数组的变更方法
的原理:
解决2:
使用Vue.set(要添加的对象,要添加的属性,要添加的属性值)
或者vm.$set(要添加的对象,要添加的属性,要添加的属性值)
这个问题也是文章开始的那个问题
所以可以解决:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<button @click="updataZs">更新张三信息</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">{{p.name}}-{{p.age}}</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
name: "yang",
persons: [
{ id: "001", name: "张三", age: 18 },
{ id: "002", name: "李四", age: 21 },
{ id: "003", name: "王五", age: 19 },
]
},
methods: {
updataZs(){
// this.persons[0].name="yang"//奏效
// this.persons[0].age="18"//奏效
// this.persons[0] = {id: "001", name: "yang", age: 18}
this.persons.splice(0,1,{id: "001", name: "yang", age: 18})
}
},
});
</script>
</body>
</html>
总结
代码总结演示
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>学生信息</h1>
<button @click="student.age++">点击按钮年龄+1</button>
<button @click="addSex">添加性别,默认值是男</button>
<button @click="updateSex">修改性别</button>
<button @click="addFriend">在列表首部添加朋友</button>
<br>
<button @click="updateFirstFriendName">修改列表中第一个朋友的名字为yang</button>
<button @click="addHobby">添加一个爱好</button>
<button @click="updateHobby">修改第一个爱好为"开车"</button>
<button @click="removeCar">过滤掉爱好"开车"</button>
<h2>姓名:{{student.name}}</h2>
<h2>年龄:{{student.age}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<!-- 如果一个对象的某属性未定义,返回undefined,vue对于undefined 类型的数据不显示,也不报错 -->
<h2>爱好:</h2>
<ul>
<li v-for="(hobby,index) in student.hobbies" :key="index">{{hobby}}</li>
</ul>
<h2>朋友:</h2>
<ul>
<li v-for="friend in student.friends" :key="friend.id">
{{friend.id}}-{{friend.name}}-{{friend.age}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
student: {
name: "yang",
age: "18",
hobbies: ["吃饭", "睡觉", "打豆豆"],
friends: [
{ id: "001", name: "张三", age: 18 },
{ id: "002", name: "李四", age: 19 },
{ id: "003", name: "王五", age: 20 },
{ id: "004", name: "赵六", age: 21 },
],
},
},
methods: {
addSex() {
Vue.set(this.student, "sex", "男");
},
updateSex() {
if(!this.student.sex) return;
if (this.student.sex === "男") {
this.student.sex = "女";
}else{
this.student.sex = "男";
}
},
addFriend(){
// 修改数组,但是使用unshift修改数据页面可以更新
this.student.friends.unshift({ id: "005", name: "老七", age: 21 })
// 并且给新加入的对象的属性设置getter和setter方法
},
updateFirstFriendName(){
// 由于vue为新加入的对象的属性设置getter和setter方法.所以这里可以直接为属性赋值
// (没有getter和setter方法的是数组中的元素,但是数组中的元素的属性是有getter和setter方法的)
this.student.friends[0].name = "yang"
},
addHobby(){
this.student.hobbies.push("学习")
},
updateHobby(){
Vue.set(this.student.hobbies, 0, "开车");
// this.student.hobbies.splice(0,1,"开车")
},
removeCar(){
this.student.hobbies = this.student.hobbies.filter((h)=>{
return h!=="开车"
})
}
},
});
</script>
</body>
</html>
文字总结
-
vue会监视data中
所有层次
的数据。 -
如何监测对象中的数据?
通过setter
实现监视,且要在new Vue时就传入要监测的数据(即在data中定义好要检测的数据)。
(1). 对象中后追加的属性
,Vue默认不做响应式处理
(2). 如需给后添加的属性做响应式,请使用如下API:
Vue.set(target.propertyName/indexvalue)
或vm.$set(target.propertyName/index,value)
-
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1) 调用原生对应的方法对数组进行更新。
(2) 重新解析模板,进而更新页面。 -
在Vue修改数组中的某个元素一定要用如下方法:
(1) 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(这些操作都修改了原数组)
(2) Vue.set()或 vm.$set()
对于不修改原数组的操作直接使用data中已经定义好的数据接收即可。 -
特别注意:Vue.set()和 vm.$set()不能给
vm
或vm的根数据对象
(即不能修改data的属性)添加属性!!!
即不能直接向data中添加属性。
数据劫持
实际上数据劫持就是说为data数据添加响应式getter和setter的过程。添加完响应式getter和setter之后,只要数据一改变,就会被setter"劫持"
,进而重新解析模板。