声明:是接着上篇讲的哦,感兴趣可以去看一看~
这里一些代码就不写了,为了缩减代码量,大家知道就可以了: Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
热身小tips,可以安装这个插件,这样写代码有提示哦~
一、Vue核心(中篇)
1.9 监视属性(侦听属性)
使用计算属性确定天气案例
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{info}}</h2>
<!-- 绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句 -->
<!-- <button @click="isHot = !isHot">切换天气</button> -->
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
})
</script>
所有vue写成的函数都要写成普通函数,不要使用箭头函数,因为箭头函数牵涉到没有this的问题
监视属性watch:
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
2.监视的属性必须存在,才能进行监视!!
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
this.isHot = !this.isHot
}
},
3.监视的两种写法:
(1).new Vue时传入watch配置
(2).通过vm.$watch监视
/* watch:{
isHot:{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
} */
})
vm.$watch('isHot',{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})
</script>
深度监视
<!--
深度监视:
(1).Vue中的watch默认不监测对象内部值的改变(一层)。
(2).配置deep:true可以监测对象内部值改变(多层)。
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2).使用watch时根据数据的具体结构,决定是否采用深度监视。
-->
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
<hr/>
<h3>a的值是:{{numbers.a}}</h3>
<button @click="numbers.a++">点我让a+1</button>
<h3>b的值是:{{numbers.b}}</h3>
<button @click="numbers.b++">点我让b+1</button>
<button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
{{numbers.c.d.e}}
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#root',
data:{
isHot:true,
numbers:{
a:1,
b:1,
c:{
d:{
e:100
}
}
}
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
watch:{
isHot:{
// immediate:true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
},
//监视多级结构中某个属性的变化
/* 'numbers.a':{
handler(){
console.log('a被改变了')
}
} */
//监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){
console.log('numbers改变了')
}
}
}
})
</script>
监视属性的简写
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeweather">切换天气</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#root',
data: {
//布尔值 可以直接被识别出来?
isHot: true
},
computed: {
info () {
return this.isHot ? '炎热' : '凉爽'
}
}, methods: {
changeweather () {
this.isHot = !this.isHot
}
},
watch: {
// isHot: { //对象
// // immediate: true,
// // deep: true, //深度监视
// handler (newValue, oldValue) {
// console.log('info被修改了', newValue, oldValue)
// }
// }
// 只有handler配置项可以简写 ()就代表handler函数
isHot (newValue, oldValue) {
console.log('info被修改了', newValue, oldValue)
}
}
})
// vm.$watch('isHot', {
// handler (newValue, oldValue) {
// console.log('info被修改了', newValue, oldValue)
// }
// })
vm.$watch('isHot', function (newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue)
})
//简写
/* vm.$watch('isHot',(newValue,oldValue)=>{
console.log('isHot被修改了',newValue,oldValue,this)
}) */
</script>
天气案例中计算属性和侦听属性区分:
<!-- 准备好一个容器-->
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
firstName(val){
setTimeout(()=>{
console.log(this)
this.fullName = val + '-' + this.lastName
},1000);
},
lastName(val){
this.fullName = this.firstName + '-' + val
}
}
})
</script>
1.10 绑定样式
<style>
.basic {
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy {
border: 4px solid red;
;
background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg, yellow, pink, orange, yellow);
}
.sad {
border: 4px dashed rgb(2, 197, 2);
background-color: gray;
}
.normal {
background-color: skyblue;
}
.atguigu1 {
background-color: yellowgreen;
}
.atguigu2 {
font-size: 30px;
text-shadow: 2px 2px 10px red;
}
.atguigu3 {
border-radius: 20px;
}
</style>
<script type="text/javascript" src="../../js/vue.js"></script>
</head>
<body>
<!--
绑定样式:
1. class样式
写法:class="xxx" xxx可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
2. style样式
:style="{fontSize: xxx}"其中xxx是动态值。
:style="[a,b]"其中a、b是样式对象。
-->
<!-- 准备好一个容器-->
<div id="root">
<!-- 绑定class样式 字符串写法 适用于:样式的类名不确定 需要动态指定 -->
<div class="basic" :class='mood' @click="changeMood">{{name}}</div><br><br>
<!-- 绑定class样式 数组写法 适用于 帮绑定的样式个数不确定 名字也不确定 -->
<!-- 相当于拿到的是值 -->
<div class="basic" :class="['atguigu1', 'atguigu2' , 'atguigu3' ]" @click="changeMood">{{name}}</div><br><br>
<!-- 相当于拿到的是变量 还要从vm中找 -->
<!-- <div class="basic" :class="[a, b , c]" @click="changeMood">{{name}}</div><br><br> -->
<div class="basic" :class="classArr" @click="changeMood">{{name}}</div><br><br>
<!-- 对象写法 要绑定的样式个数确定、名字也确定,但要动态决定用不用-->
<!-- <div class="basic" :class="classObj" @click="changeMood">{{name}}</div><br><br> -->
<!-- 没在vm上定义 在vue开发工具添加样式不可能实现 -->
<div class="basic" :class="{ atguigu1: false,
atguigu2: false}" @click="changeMood">{{name}}</div><br><br>
<!-- 绑定style样式 对象写法 适用于:要绑定的样式个数确定、名字也确定 但要动态决定用不用 -->
<!-- <div class="basic" :style="{fontSize:fsize + 'px'}">{{name}}</div><br><br> -->
<!-- <div class="basic" :style="styleObj">{{name}}</div><br><br> -->
<!-- <div class="basic" :style="[styleObj1,styleObj2]">{{name}}</div><br><br> -->
<!-- 绑定style样式 数组写法 适用于:要绑定的样式个数确定、名字也确定 但要动态决定用不用 -->
<div class="basic" :style="styleArr">{{name}}</div><br><br>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
mood: 'normal',
classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
// a: 'atguigu1',
// b: 'atguigu2',
// c: 'atguigu3',
classObj: {
atguigu1: false,
atguigu2: false
},
// fsize: 40
// styleObj1: {
// fontSize: '60px',
// color: 'red',
// }, styleObj2: {
// backgroundColor: 'orange'
// },
styleArr: [{
fontSize: '60px',
color: 'red',
}, {
backgroundColor: 'orange'
}]
}, methods: {
changeMood () {
// // 如果这样写 直接js了 不用vue
// // document.querySelector('.basic').className = 'basic happy'
// this.mood = 'happy'
const arr = ['happy', 'sad', 'normal']
const index = Math.floor(Math.random() * 3)
this.mood = arr[index]
}
},
})
</script>
1.11 条件渲染
条件渲染:
1.v-if
写法:
(1).v-if=“表达式”
(2).v-else-if=“表达式”
(3).v-else=“表达式”
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
2.v-show
写法:v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
<div id="root">
<h2>当前的值是{{n}}</h2>
<button @click="n++">点我n+1</button>
<!-- <h2 v-show='a'>欢迎来到{{name}}</h2>
<h2 v-show="1===3">欢迎来到{{name}}</h2> -->
<!-- 用v_if做条件渲染 结构也不显示 -->
<!-- <h2 v-if='false'>欢迎来到{{name}}</h2> -->
<!-- 切换频率 如果成立 其他不成立-->
<!-- 快 高效 -->
<!-- <div v-show="n===1">a</div>
<div v-show="n===2">b</div>
<div v-show="n===3">c</div>
<div v-show="n===4">d</div> -->
<!-- 慢 低效 -->
<!-- <div v-show="n===1">a</div>
<div v-show="n===2">b</div>
<div v-show="n===3">c</div>
<div v-show="n===4">d</div> -->
<!-- 第一句找到了 后面就都不执行了 佐证 -->
<div v-if="n===1">a</div>
<div v-else-if="n===1">aa</div>
<div>@</div>//中间不能断
<div v-else-if="n===3">c</div>
<div v-else>哈哈哈</div>
<!-- 就是不会破坏结果 可以直接拿到css样式 -->
<template v-if="n===1">
<h2>1</h2>
<h2>3</h2>
<h2>4</h2>
</template>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el: '#root',
data: {
name: '哈哈哈',
a: false,
n: 0
},
methods: {}
});
</script>
</body>
1.12 列表的渲染
1.基本列表
1.遍历数组数据形成页面上的列表
思路:使用v-for遍历数组对象,然后展示在列表上
具体说来就是:js中有顺序 要用数组对象
先写一个 想生成多个li 就在谁身上写个v-for 遍历
v-for能遍历,加在谁身上 谁就能遍历,persons能决定遍历多少次
<div id="root">
<!-- 第一种写法:只有一个参数 遍历出来的是数组对象中的每一项 -->
<!-- 每一个li都有一个标识 (通过遍历) 所以有key -->
//这里in也可以用of
<li v-for="p in persons" :key="p.id">
//使用插值语法中的p可能来自三个地方:data中的属性 计算属性 还有参数,这里是参数
{{p.name}}-{{p.age}}
</li>
<!-- 第二种写法:两个参数 分别是数组对象中的每一项 索引号 -->
<!-- <li v-for="(p,index) in persons" :key="p.id"> -->
<!-- key的取值 只要保证每一项对应的key值不一样即可 -->
<li v-for="(p,index) in persons" :key="index">
<!-- {{p.name}}-{{p.age}} -->
{{p}}---{{index}}
</li>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 }
]
}
});
</script>
2.遍历对象数据形成页面上的列表
<!-- 遍历对象 -->
<li v-for="(value,key) of car" :key="key">
<!-- {{p.name}}-{{p.age}} -->
{{key}}:{{value}}
</li>
car: {
name: '奥利',
price: '70万',
color: '黑色'
}
具体来说一共五种
<body>
<div id="root">
<!-- 1.遍历数组 -->
<!-- 第一种写法:只有一个参数 遍历出来的是数组对象中的每一项 -->
<!-- 每一个li都有一个标识 (通过遍历) 所以有key -->
<li v-for="p in persons" :key="p.id">
{{p.name}}-{{p.age}}
</li>
<!-- 第二种写法:两个参数 分别是数组对象中的每一项 索引号 -->
<!-- <li v-for="(p,index) in persons" :key="p.id"> -->
<!-- key的取值 只要保证每一项对应的key值不一样即可 -->
<li v-for="(p,index) in persons" :key="index">
<!-- {{p.name}}-{{p.age}} -->
{{p}}---{{index}}
</li>
<!-- 2.遍历对象 -->
<li v-for="(value,key) of car" :key="key">
{{key}}:{{value}}
</li>
<!-- 3.遍历字符串 -->
<li v-for="(index,char) of str" :key="index">
{{index}}:{{char}}
</li>
<!-- 4.遍历指定次数 -->
<li v-for="(number,index) of 6" :key="index">
{{index}}:{{number}}
</li>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 }
],
car: {
name: '奥利',
price: '70万',
color: '黑色'
},
str: 'hello'
}
});
</script>
2.key的原理
1.按照数组 key作用:给节点进行标识
2.效率 每一个人后面加input框 p.id唯一数据标识
死记硬背:遍历列表的时候就是会有个key来标识每个节点,使用key来标识;这里如果牵涉到列表的增加或者删除,使用id来标识,如果不牵涉则使用index,如果没有写index,vue默认使用index来标识
上述图片的详细理解:
1.拿到刚开始的数据,也就是还没有添加老刘
2.vue会将初始数据生成虚拟DOM,(加了key) 此时页面中没有数据,内存中有
3.vue将虚拟DOM转化为真实DOM,真实DOM才是用户能看到的,用户才可以在input框输入
4.添加老刘,更新数据,生成了新的数据
5.vue根据新数据生成虚拟DOM
6.因为是第一条的添加位置,所以老刘的key对应为0
7.vue会开启虚拟DOM对比算法 也就是新旧虚拟DOM对比
8.按照顺序,从key为0开始对比,这里的老刘-30属于文本节点,input框属于标签节点。key=0时,文本节点不同,则老刘-30生成新的数据,从虚拟DOM转化为真是DOM;标签节点相同(,这里只看虚拟DOM,单纯诸葛词语对比,因为值对比虚拟DOM,不要看真实DOM),实现复用。也就是旧的虚拟DOMinput框一定转化为真实的DOM,所以这里会拿之前变好的input真实DOM框,实现复用。
9.挨个对比,相同的直接用之前的,不同的直接下来(直接生成真实DOM)
10.这里到了王2-50 key=3,找不到与他相同的 所以新的虚拟DOM直接转化为真实的DOM,那么真实DOM的input框下来的时候,用户还没有填数据,就为空
从这里就可以看出:效率低(因为错乱的数据都不能使用,统统都需要vue工作由虚拟DOM转化为真实DOM,都是需要重新生成,错误DOM更新) 并且数据错乱
另外:如果将王五追加在后面没有问题,但是对数据破坏顺序的操作,就不能使用index。而需要使用数据唯一标识id
另外 不写key的值 index在遍历的时候vue会自动补充,index会取遍历时的索引值 往后加push 好用
手机、邮箱、用户,vue不可能采集到这些信息,所以只能通过数据库录入的方式 id:001
有上述可知,再把列表项追加到第一位时要用标识: p.id 如下图 想改成index 直接在下面改即可
<body>
<div id="root">
<!-- 1.在persons的上方追加老刘 绑定click事件并且只添加一次-->
<h2>人员列表</h2>
<button @click.once="add">添加一个老刘</button>
<!-- //发现html结构中没有key 因为他是在vue内部使用 转化为真实的dom之后就丢弃了 -->
<li v-for="p in persons" :key="p.id">
{{p.name}}-{{p.age}}
<input type="text">
<!-- //2. 这里加上input框 是为了实现在每一个对象后都有一个对应的input框
// 会发现当我们填写之后添加老刘,就会出现错乱的情况 不仅仅是效率低下的问题
// 此时使用p.id(数据的唯一标识)不会有问题 这里就开始引入key工作原理和虚拟dom对比算法-->
</li>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 }
]
}, methods: {
add () {
const p = { id: '004', name: '老刘', age: 40 }
this.persons.unshift(p)
}
}
});
</script>
3.列表过滤
<body>
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyword">
<ul>
<li v-for="p in filterPersons">
{{p.name}}-{{p.age}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示
new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 }
],
keyword: '',
// filterPersons: []
},
// watch: {
// keyword: {
// immediate: true,//一上来就调用 用户什么也没输入的情况下 这样可以获得完整的数据
// // 死记硬背:通过watch监听 能够知道keyword被修改了
// // console.log('keyword被修改了', val)
// // 这里的p与上文li中的p不是一个变量 只不过名字相同 根据filter语法 p标识数组对象中的每一个对象
// // 但是这里有一个问题:就是filter返回的新数组给了persons 这导致persons数据缺失 所以需要新的空数组接收
// // this.persons = this.persons.filter((p) => {
// // return p.name.indexOf(val) !== -1
// // })
// // 每次都从persons过滤数据可以避免 但是这里有一个问题 在没有过滤的情况下不展示所有数据 解决办法: immediate: true
// handler (val) {
// this.filterPersons = this.persons.filter((p) => {
// return p.name.indexOf(val) !== -1
// })
// }
// }
// }
// computed返回值就是返回真正的过滤结果 filterPersons 依赖keyword发生变化 computed的返回值就是结果
computed: {
filterPersons () {
return this.persons.filter((p) => {
return p.name.indexOf(this.keyword) !== -1
})
}
}
});
</script>
4.列表排序
思路如下图:
思路
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyword">
<!-- //1.不同按钮 用不同序号标识 然后根据不同的sortType值知道选的哪个button -->
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="p in filterPersons">
{{p.name}}-{{p.age}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示
new Vue({
el: '#root',
data: {
sortType: 0, // 0,1,2分别是原顺序 年龄降序 年龄升序
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 44 },
{ id: '003', name: '王五', age: 20 },
{ id: '004', name: '赵四', age: 22 },
],
keyword: '',
},
// computed返回值就是返回真正的过滤结果 filterPersons 依赖keyword发生变化 computed的返回值就是结果
computed: {
filterPersons () {
// 2.需要明确:过滤+排序都是在filterPersons基础之上进行的
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyword) !== -1
})
//if 语句中使用一个数值时,该数值会被隐式地转换为布尔值
//这一次提到sortType是为了判断是不是为0 布尔值中零代表false 非零值代表true
if (this.sortType) {
arr.sort((p1, p2) => {
//这一次提到sortType是为了判断是不是为1
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
})
}
return arr
}
}
});
</script>
这里分不清是升序还是降序可以直接试一下 不是升序就是降序
let arr = [1, 6, 8, 43, 44];
arr.sort(function (a, b) {
return a - b; // 升序排序
});
console.log(arr)
5.更新时的一个问题
会出现修改数据不奏效的问题,这是因为直接拿到数组的索引值来改变数据是不能奏效的,Vue内部不答应,我们可以用splice语句一集下问题道德Vue.set语句 ,下面6,7,8,9都会一直在探讨这个问题
<!-- 准备好一个容器-->
<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>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'马冬梅',age:30,sex:'女'},
{id:'002',name:'周冬雨',age:31,sex:'女'},
{id:'003',name:'周杰伦',age:18,sex:'男'},
{id:'004',name:'温兆伦',age:19,sex:'男'}
]
},
methods: {
updateMei(){
// this.persons[0].name = '马老师' //奏效
// this.persons[0].age = 50 //奏效
// this.persons[0].sex = '男' //奏效
// this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效
this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'})
}
}
})
</script>
6.Vue监测数据改变的原理_对象
先探讨Vue检测对象的数据改变,再检测数组数据的改变
首先我们知道data数据中的属性最终会放在vm中,但是data数据到vm数据需要经历两步:
Vue中实现数据监视:
1.加工data
2.vm._data=data
reactive代表响应式 是数据改变页面也会改变
7.模拟一个数据监测
步骤一:大家可能会想既然只要检测到data中数据的改变页面也会改变,并且控制条也要输出,那我只要自己写不就可以了吗?
<script>
let data = {
name: '哈哈哈',
address: '河南'
}
// 如果不写定时器 没办法实时检测数据的改变 所以采用定时器进行数据的实时监测
// 并且需要引入变量tmp 这样做是为了让name值最后恢复与temp相同的值 省的代码一直被检测到发生变化,也就是让他们每次都保证相同 只进行数值发生改变的时候才会有变化
let tmp = '哈哈哈'
setInterval(() => {
if (data.name !== '哈哈哈') {
console.log('name被修改了', name)
}
}, 100)
</script>
缺点:我们总不能每次数据改变就去开定时器吧,所以监测数据还是采用getter和setter
步骤二:我们试试使用Object.defineProperty匹配getter和setter
<script>
let data = {
name: '哈哈哈'
}
Object.defineProperty(data, 'name', {
get () {
return data.name
},
set (val) {
data.name = val
console.log('name被修改')
}
})
</script>
结果表示内存溢出:
原理:重复调用 不会停
使用Obverser方法检测数据属性的变化
<script>
let data = {
name: '哈哈哈',
address: '河南'
}
const obs = new Observer(data)
// 准备一个实例
let vm = {}
//将我们写出来的obs给data和vm._data
vm._data = data = obs
//创建一个监视的实例对象 用于监视data中的属性的变化
function Observer (obj) {
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
//遍历
keys.forEach((k) => {
Object.defineProperty(this, k, {
get () {
//obj[k] 则是使用方括号语法来访问或设置 obj 对象上对应名称的属性。这是正确的做法,
//因为它允许使用变量来动态地引用对象的属性
return obj[k]
}, set (val) {
console.log(`${k}被改变了,我要去解析模版,生成虚拟dom,。。。我要开始忙了`)
obj[k] = val
}
})
})
}
</script>
缺点1:改变属性的时候必须全称 并且我们改变属性值之后会立刻显示在页面上
缺点2:对象中还有对象 多层 使用Observer只能监测一层对象属性,对象中如果嵌套对象,则不能检测;而Vue监测则是多层监测,直到找到不是对象的才罢休
模拟的数据监测:
Vue监测的数据:
所以引出了大牛还得数Vue监测数据的改变 ;只要改变属性 就能调用setter接着解析模版
8.Vue.set()方法
步骤一:要求学生添加性别,属于动态添加,譬如:用户点击了才会添加性别,所以我们不能直接添加:这里有一些问题需要理解:如果我们定义在student下的性别,在模版中写出可以在页面展示出来;如果没有在data中定义,写student.sex不会报错,因为只是未定义,undefined不会报错;但如果只是写sex就会报错
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr>
<h2>姓名:{{student.name}}</h2>
<!-- <h2>性别:{{student.sex}}</h2> -->
<!-- <h2>性别:{{sex}}</h2> -->
<!-- //不会报错 -->
<h2>性别:{{undefined}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<ul>
<!-- 参数 如果有两个参数 那么用小括号 并且因为只是将数组元素展示在页面上 不涉及元素的添加和删除 直接index -->
<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 //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#root',
data: {
name: '哈哈哈',
address: '南京',
student: {
age: {
rAge: 19,
sAge: 29
},
friends: [
{ name: 'jerry', age: 35 },
{ name: 'tony', age: 36 }
],
// sex: '男'
}
}
});
</script>
步骤二:如果在控制台动态添加性别,会发现不会展示在页面上 后添加的不会有对应的getterhesetter 也就是不存在响应式
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr>
<h2>姓名:{{student.name}}</h2>
<h2>性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<ul>
<!-- 参数 如果有两个参数 那么用小括号 并且因为只是将数组元素展示在页面上 不涉及元素的添加和删除 直接index -->
<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 //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#root',
data: {
name: '哈哈哈',
address: '南京',
student: {
name: 'tom',
age: {
rAge: 19,
sAge: 29
},
friends: [
{ name: 'jerry', age: 35 },
{ name: 'tony', age: 36 }
],
// sex: '男'
}
}
});
</script>
所以根据Vue内部特性 也就是需要想用什么事先添加好 想要做到动态添加特性 需要是Vue.set()
步骤三:首先控制台打印:
两种添加响应式属性的方法:
第一种:Vue调用的API:Vue.set()
第一个参数表示往谁身上追加属性;第二个是追加的属性 第三是追加的属性值
第二种:vm.$set()
使用数据代理的方法知道:通过Object.defineProperty()原本是修改data上的属性转化为修改vm上的属性
使用button按钮添加
<div id="root">
<h2>学校名称:{{school.name}}</h2>
<h2>学校地址:{{school.address}}</h2>
<h2>学校校长:{{school.leader}}</h2>
<hr>
<h1>学生信息</h1>
<button @click="addSex">添加一个性别属性,默认值是男</button>
<h2>姓名:{{student.name}}</h2>
<!-- //有性别则展示 没有性别不展示 -->
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<ul>
<!-- 参数 如果有两个参数 那么用小括号 并且因为只是将数组元素展示在页面上 不涉及元素的添加和删除 直接index -->
<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 //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#root',
data: {
school: {
name: '哈哈哈',
address: '南京',
},
student: {
name: 'tom',
age: {
rAge: 19,
sAge: 29
},
friends: [
{ name: 'jerry', age: 35 },
{ name: 'tony', age: 36 }
],
// sex: '男'
}
}, methods: {
addSex () {
Vue.set(this.student, 'sex', '女')
}
}
});
</script>
有局限:
必须在data中某一个对象中添加属性 不能直接在data下追加属性
9.Vue监测数据的改变_数组
Vue内部没有为数组匹配对应的getter和setter
Vue中使用push通过包装的思想,也就是Vue中使用的push,不是数组上原型对象上的push,而是Vue的push会经过两步:
1.调用原型对象上的push,2.重新模版解析,然后数组更新检测,对数据进行增删改查
并且返回的是真实能够影响到数组的,例如:filter 不能影响到数组 没有返回值
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<h1>爱好</h1>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<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 //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#root',
data: {
name: '哈哈哈',
address: '南京',
student: {
name: 'tom',
age: {
rAge: 19,
sAge: 29
},
friends: [
{ name: 'jerry', age: 35 },
{ name: 'tony', age: 36 }
],
hobby: ['抽烟', '喝酒', '烫头']
}
}
});
</script>
在Vue修改数组中的某个元素一定要用如下方法:
1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse() 2.Vue.set() 或 vm.$set(),
理论来源
所以更新时的问题,修改数据的解决办法是:不能使用数组元素的修改方法 而应该通过包装了的vue语句
使用Vue.set() vm.$set()来实现替换 也是响应式 但是用得不多
10 总结Vue数据检测
<div id="root">
<h1>学生信息</h1>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<!-- 2.当时没有的 添加一个性别 没有性别不要再出现 -->
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<!-- 1.逻辑简单 直接加加 -->
<button @click="student.age++">年龄一点一加</button><br><br>
<!-- 2.当时没有的 添加一个性别 没有性别不要再出现 -->
<button @click="addSex">添加性别属性,默认值:男</button><br><br>
<!-- 3.修改性别 里面是正常的js表达式 由于逻辑简单 直接写 -->
<button @click="student.sex='未知'">修改性别</button><br><br>
<!-- 4.使用unshift列表数组元素首位添加属性 对象里的属性是响应式的 -->
<button @click="addFriend">在列表首位添加元素</button><br><br>
<!-- 5.修改第一个朋友的名字为张三-->
<button @click="updateFirends">修改第一个朋友的名字为张三</button><br><br>
<!-- 6.添加爱好 -->
<button @click="addHobby">添加爱好</button><br><br>
<!-- 7.修改爱好 -->
<button @click="updateHobby">修改第一个爱好为:开车</button><br><br>
<!-- 8.Vue检测不到filter的新数组变化 因为不会返回新数组,我们自己把原来的数组替换 过滤掉爱好中的抽烟 -->
<button @click="removeSmoke">过滤掉爱好中的抽烟</button>
<h3>爱好</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<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 //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#root',
data: {
student: {
name: 'tom',
age: 29,
friends: [
{ name: 'jerry', age: 35 },
{ name: 'tony', age: 36 }
],
hobby: ['抽烟', '喝酒', '烫头']
}
}, methods: {
addSex () {
// this.student.sex = '男'
// 一开始没有 后来有的
vm.$set(this.student, 'sex', '男')
}, addFriend () {
this.student.friends.unshift({ name: 'amy', age: 23 })
}, updateFirends () {
//可是直接按照数组的形式来写 因为是对象数组索引值赋值修改,Vue不承认,没有对应的egetter和setter;
//但是对象中有属性就会有getter和setter
this.student.friends[0].name = '张三'
}, addHobby () {
this.student.hobby.push('学习')
}, updateHobby () {
//不能直接通过数组索引值修改
// this.student.hobby.splice(0, 1, '开车')
Vue.set(this.student.hobby, 0, '开车')
}, removeSmoke () {
this.student.hobby = this.student.hobby.filter((h) => {
return h !== '抽烟'
})
}
}
});
</script>
数据劫持:有人修改了student,对应的setterr会被调用(感知),然后模版解析
总结:
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的根数据对象 添加属性!!!