计算属性

计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例。

此处fullname调用了四次,但是1只执行了一次。get的作用:当fullName被读取的时候时,get就会被调用,且返回值就作为fullName的值。

计算属性的结果会被缓存,除非依赖的响应式 property 变化才会重新计算。注意,如果某个依赖 (比如非响应式 property) 在该实例范畴之外,则计算属性是不会被更新的。

get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div id="root" style="margin: 121px 366px;">
姓氏<input type="text" v-model="firstname"><br/>
<input type="text" v-model="nextname"><br/>
姓名:<span>{{fullname}}</span><br/>
姓名:<span>{{fullname}}</span><br/>
姓名:<span>{{fullname}}</span><br/>
姓名:<span>{{fullname}}</span><br/>
</div>
<script>
Vue.config.productionTip= false
const vm = new Vue({
el:'#root',
data:{
firstname:'张',
nextname:'三'
},
computed:{
fullname:{
get(){
console.log(1)
return this.firstname + this.nextname
}
}
}
})
</script>

image-20211215165055701

  • 定义:要用的属性不存在,要通过已有属性计算得来。

  • 原理:底层借助了Objcet.defineproperty方法提供的getter和setter。

  • 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。

  • 备注:

    计算属性最终会出现在vm上,直接读取使用即可。

    如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

  • 简写

    1
    2
    3
    4
    5
    computed:{
    fullname(){
    return this.firstname + this.nextname
    }
    }

监视 属性

  1. 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
  2. 监视的属性必须存在,才能进行监视!!
  3. 监视的两种写法:
    • new Vue时传入watch配置
    • 通过vm.$watch监视,传两个参数,一个属性,一个方法('isHot',{handler(){}})

写法一:

1
2
3
4
5
6
7
8
watch:{
isHot:{
immediate:true, //初始化时让handler调用一下
handler(newvalue,oldvalue){
console.log("isHot被更改了",newvalue,oldvalue)
}
}
}

写法二:

1
2
3
4
5
6
7
8
vm.$watch('isHot',
{
immediate:true,
handler(newvalue){
console.log("isHot被更改啦",newvalue);
}
}
)

深度监视

  1. Vue中的watch默认不监测对象内部值的改变(一层)。
  2. 配置deep:true可以监测对象内部值改变(多层)。

​ 备注:

​ (1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!

​ (2).使用watch时根据数据的具体结构,决定是否采用深度监视。

​ (3).deep默认为false为了提高开发效率,

1
2
3
4
5
6
7
8
9
vm.$watch('number',
{
immediate:true,
handler(newvalue){
console.log("number被更改啦",newvalue);
},
deep:true
}
)

简写

简写的话,不能够添加deep,immediate属性。

简写一:

1
2
isHot(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue,this)

简写二:

1
2
3
vm.$watch('isHot',function(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue,this)
})

计算属性和监测属性对比

如下代码证明watch能执行异步操作,但是computed不行,setTimeout必须是箭头函数,this指向所定义的作用域内的Vue实例对象,setTimeout下的回调函数不是Vue实例对象调用的,而是V8浏览器的js引擎调用的,所以不使用箭头函数的话,this指向window。

1
2
3
4
5
6
7
8
9
10
11
watch:{
firstName(val){
setTimeout(()=>{
console.log(this)
this.fullName = val + '-' + this.lastName
},1000);
},
lastName(val){
this.fullName = this.firstName + '-' + val
}
}

​ computed和watch之间的区别:

  1. computed能完成的功能,watch都可以完成。
  2. watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。

​ 两个重要的小原则:

  1. 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
  2. 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数这些都是异异步的),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。

绑定class样式

​ 写法:class=”xxx” xxx可以是字符串、对象、数组。

​ 字符串写法适用于:类名不确定,要动态获取。

​ 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。

​ 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。

1
2
3
4
5
6
7
8
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 ,比如mood确定但是mood的值是normal,happy,sad不确定-->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>

<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div> <br/><br/>

<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div> <br/><br/>

绑定style样式

​ :style=”{fontSize: xxx}”其中xxx是动态值。

​ :style=”[a,b]”其中a、b是样式对象。

1
2
3
4
5
6
7
8
9
data:{

showin:{
backgroundColor:'orange',
fontSize: '50px',
margin:'auto',
textAlign:'center'
}
},

条件渲染

1
2
3
4
5
6
7
8
9
10
<div id="root">
<h2>hello,{{n}}</h2>
<button @click="n++">anniu</button>
<div v-if="n===1">a</div>
<div v-else-if="n===2">b</div>
<div v-else-if="n===3"> c</div>
<!-- <div v-show="n===1">a</div>
<div v-show="n===2">b</div>
<div v-show="n===3">c</div> -->
</div>

v-if

​ 写法:

​ (1).v-if=”表达式”

​ (2).v-else-if=”表达式”

​ (3).v-else,else后面无需跟条件。

​ 适用于:切换频率较低的场景。

​ 特点:不展示的DOM元素直接被移除

​ 注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。

v-show

​ 写法:v-show=”表达式”

​ 适用于:切换频率较高的场景。

​ 特点:不展示的DOM元素不会被移除,vue内部添加style="display :none",仅仅是使用样式隐藏掉

​ 3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。

列表渲染

​ 1.用于展示列表数据

​ 2.语法:v-for=”(item, index) in xxx” :key=”yyy”

遍历数组:p是数组中的每个数据,index是当前索引(用的最多的一种)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 <div id="root">
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}}-{{p.age}}
</li>
</ul>
</div>
<script>
new Vue({
el:'#root',
data:{
persons:[
{id:'01',name:'张三',age:'18'},{
id:'02',name:'李四',age:'20'
},{
id:'03',name:'王五',age:'18'
}
]
}
})
</script>

遍历对象:value是值,k是属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	<ul>
<li v-for="(value,k) of car" :key="k">
{{k}}-{{value}}
</li>
</ul>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{

car:{
name:'奥迪A8',
price:'70万',
color:'黑色'
},
str:'hello'
}
})
</script>

Key的作用和原理

image-20211217153507399

image-20211217154249202

  • 虚拟DOM中key的作用:

    变化时,Vue会根据【新数据】生成【新的虚拟DOM】,

    随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

  • 对比规则:

(1).旧虚拟DOM中找到了与新虚拟DOM相同的key

①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

(2).旧虚拟DOM中未找到与新虚拟DOM相同的key,创建新的真实DOM,随后渲染到到页面。

  • 用index作为key可能会引发的问题:

    破坏顺序操作:

    会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低,逆序操作重新生成张三李四王五。

如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。

开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。

列表过滤

watch监测属性实现

  1. 创建persons属性,创建一个filterper数组为了避免更改原来的数据。

  2. 添加watch属性,返回this.filterper的值,因为空字符串也是包含在字符串中,使用immediate:true让数据能够全部显示。

    image-20211217163814039

  3. this.filterper= this.persons.filter((p)=>{return p.name.indexOf(val)!== -1}) ,即是用arr.filter()的回调判断arr.indexOf()的返回值如果为-1就不存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
     <div id="root">
<ul>
<input type="text" v-model="keywords">
<li v-for="(p,index) in filterper" :key="p.id">
{{p.name}}-{{p.age}}
</li>
</ul>

</div>
<script>
new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
keywords:'',
filterper:[]
},
watch:{
keywords:{
immediate:true,
handler(val){
this.filterper= this.persons.filter((p)=>{
return p.name.indexOf(val)!== -1
})
}
}
}
})
</script>

computed计算属性实现

  1. 第一个return是计算属性的返回值,第二个return是filter回调函数的返回值。
1
2
3
4
5
6
7
computed:{
filPerons(){
return this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1
})
}
}

列表排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    computed:{
filterpers(){
const arr= this.persons.filter((p)=>{
return p.name.indexOf(this.keywords) !== -1
})
if(this.sorttype){

arr.sort((a,b)=>{
return this.sorttype=== 1 ? a.age -b.age :b.age -a.age
})
}
return arr
}
}

Vue监听数据原理

对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 核心数据响应式方法
function defineReactive(obj,key,val){
// val可能还是个对象,需要递归一下
objserve(val)
Object.defineProperty(obj,key,{
get(){
return val
},
set(newVal){
if(newVal !== val){
val = newVal
// 如果改变的是个对象,还需要调用一下
objserve(newVal)
console.log('set', newVal);
// 在这里已经监听到了数据的变化,后续可以做一些更新视图的操作
}
}
})
}
// 如果一个对象有多个属性,循环调用defineReactive,传入每一个值去进行监听
function objserve(obj){
// 判断obj类型
if(Object.prototype.toString.call(obj)!=="[object Object]"){
return
}
Object.keys(obj).forEach(key =>defineReactive(obj,key,obj[key]))
}
// 如果给对象增加新的属性,是监听不到变化的,那么需要用set方法传入新对象,调用defineReactive手动监听一下
function set(obj,key,val){
defineReactive(obj,key,val)
}
const obj = {foo:'foo',baz:{a:1}};
objserve(obj)
obj.foo = '2222'
obj.baz.a = '3333'
obj.baz = {a:10}
obj.baz.a = 100
set(obj,"dong",'dong')
obj.dong = "dong1"

数组

变更方法

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

你可以打开控制台,然后对前面例子的 items 数组尝试调用变更方法。比如 example1.items.push({ message: 'Baz' })

Vue.set()

Vue.set( target, propertyName/index, value )

  • 参数

    • {Object | Array} target
    • {string | number} propertyName/index
    • {any} value
  • 返回值:设置的值。

  • 用法

    向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')

    注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

只能在data的对象中添加Vue.set(),而不能在data上直接添加,也就是不能在vm和vm._data上添加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const vm = new Vue({
el:'#root',
data:{
school:{
name:'尚硅谷',
address:'北京',
},
student:{
name:'tom',
age:{
rAge:40,
sAge:29,
},
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods: {
addSex(){
// Vue.set(this.student,'sex','男')
this.$set(this.student,'sex','男')
}
}
})

总结

​ 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的根数据对象 添加属性!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

methods: {
addSex(){
// Vue.set(this.student,'sex','男')
this.$set(this.student,'sex','男')
},
addFriend(){
this.student.friends.unshift({name:'jack',age:70})
},
updateFirstFriendName(){
this.student.friends[0].name = '张三'
},
addHobby(){
this.student.hobby.push('学习')
},
updateHobby(){
// this.student.hobby.splice(0,1,'开车')
// Vue.set(this.student.hobby,0,'开车')
this.$set(this.student.hobby,0,'开车')
},
removeSmoke(){
this.student.hobby = this.student.hobby.filter((h)=>{
return h !== '抽烟'
})
}