:知不足而奋进,望远山而前行
更多Vue知识请点击——Vue.js
顾名思义,计算属性就是计算出来的属性,英文名computed。
这里要和data和methods里的东西区分,data里的属性,methods里的函数,你写的都会原封不动放到vm里,但是computed里面的东西写的是对象,最后放到vm里的是这个对象的名,值是get里的返回值。
使用插值语法可以实现,但是如果需求复杂,里面的逻辑写一大堆,不便于观察,违背了Vue的推荐风格(简单表达式)。
使用methods不错,但是有一个问题,就是每次用到fullName都会调用一次,缓存太多
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>姓名案例-插值语法实现和methods实现title>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root">
姓:<input type="text" v-model:value="firstName" /><br />
名:<input type="text" v-model="lastName" /><br />
全名:<span>{{firstName.slice(0,3)}}-{{lastName}}span><br />
全名:<span>{{fullName()}}span><br />
全名:<span>{{fullName()}}span><br />
div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
firstName: "张",
lastName: "三",
},
methods: {
fullName() {
console.log("调用");
return this.firstName + "-" + this.lastName;
},
},
});
script>
body>
html>
1、没有缓存问题,第一次调用之后就不会再调用,节省内存
2、计算属性里面的属性要写成对象的形式,每个对象里面都有getter和setter
3、fullName实际上就是firstName和lastName经过一番计算得到的玩意儿,直接到vm身上成为vm的属性
4、当有人读取fullName时,get调用,返回值作为fullName的值
5、set什么时候调用? fullName被修改时调用,这里边呢有个连锁反应,我手动修改vm.fullName导致firstName和lastName被修改,Vue模板重新解析,页面刷新,firstName和lastName的修改又导致get被重新执行(依赖的数据变了),返回新的fullName,计算属性计算属性,就是一直在计算,所以要想改fullName,必须改它依赖的值才行。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>姓名案例-计算属性实现title>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root">
姓:<input type="text" v-model:value="firstName" /><br />
名:<input type="text" v-model="lastName" /><br />
全名:<span>{{fullName()}}span><br />
全名:<span>{{fullName()}}span><br />
全名:<span>{{fullName()}}span>
div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
firstName: "张",
lastName: "三",
},
computed: {
//计算属性里面的属性要写成对象的形式,每个对象里面都有getter和setter
//这个fullName实际上就是firstName和lastName经过一番计算得到的玩意儿,直接给到vm身上
fullName: {
//get有什么用?当有人读取fullName时,get调用,返回值作为fullName的值
//get什么时候调用? 1.初次读取fullName 2.get里用到的数据发生了改变
get() {
//此处的this是vm
return this.firstName + "-" + this.lastName;
},
//set什么时候调用? fullName被手动修改时调用
//这里边呢有个连锁反应,我手动修改vm.fullName导致firstName和lastName被修改,Vue模板重新解析
//页面刷新,firstName和lastName的修改又导致get被重新执行(依赖的数据变了),返回新的fullName
//计算属性计算属性,就是一直在计算,所以要想改fullName,必须改它依赖的值
set(value) {
const arr = value.split("-");
this.firstName = arr[0];
this.lastName = arr[1];
},
},
},
});
script>
body>
html>
定义:要用的属性不存在,要通过Vue中已有属性计算得来。
原理:底层借助了Objcet.defineproperty
方法提供的getter和setter。
get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
备注:
1.计算属性最终会出现在vm上,直接读取使用即可,不用加括号,和methods区别一下子。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变,这很关键。
计算属性在实际开发中更多的情况是只读取不修改的,这种只读不写的时候可以使用简写:
<!-- 准备好一个容器 -->
<div id="root">
姓: <input type="text" v-model="firstName"><br><br>
名: <input type="text" v-model="lastName"><br><br>
<!-- 拼接姓名实现"张-三"联动 -->
<!-- 使用计算属性,只调用一次,节省内存 -->
姓名: <span>{{fullName}}</span>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
//简写
computed: {
fullName() {
console.log('fullName被读取了');
return this.firstName + '-' + this.lastName;
}
}
})
</script>
这里一定要注意,这个fullName是一个属性,不是一个方法,只不过是调用里面的getter,把返回值直接给了vm罢了,所以直接{{fullName}}就可以搞出来,不用加小括号调用!!
实现点击按钮切换天气
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>天气案例title>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root">
<h2>今天天气很{{isHot ? '炎热': '寒冷'}}h2>
<h2>今天天气很{{info}}h2>
<button @click="changeWeather">切换天气button>
div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? "炎热" : "凉爽";
},
},
methods: {
changeWeather() {
//注意这里的isHot要加this
this.isHot = !this.isHot;
},
},
});
script>
body>
html>
1、监视属性里有个handler函数,当被监视的属性(例如isHot)被修改时调用handler函数
2、handler函数传两个参数,分别是:(修改之后的值,修改之前的值)
3、监视的属性必须存在,才能进行监视!!既可以是data里的属性(isHot),也可以是计算属性(info)
<div id="root">
<!-- 实现点击按钮切换天气 -->
<!-- 写法1:利用插值语法和三元表达式 -->
<h2>今天天气很{{isHot ? '炎热': '寒冷'}}</h2>
<!-- 写法2:利用计算属性 -->
<h2>今天天气很{{info}}</h2>
<!-- 绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句,但是最好别这么干-->
<!-- <button @click="isHot = !isHot">切换天气</button>-->
<button @click='change'>点击切换天气</button>
</div>
<script>
new Vue({
el: '#root',
data: {
isHot: true
},
methods: {
change() {
this.isHot = !this.isHot;
}
},
computed: {
info() {
//注意这里的isHot要加this
return this.isHot ? '炎热' : '寒冷';
}
},
watch: {
isHot: {
immediate: true, //初始化时先调用一次handler
//当isHot被修改时调用handler函数
handler(newVal, oldVal) {
console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
}
},
//watch不只可以监视data中的属性,还可以监视计算属性
info: {
immediate: true, //初始化时先调用一次handler
//当isHot被修改时调用handler函数
handler(newVal, oldVal) {
console.log('info被修改了', '改之前是' + oldVal, '改之后是' + newVal);
}
}
}
})
</script>
②通过vm.$watch监视
const vm = new Vue({
el: '#root',
data: {
isHot: true
},
methods: {
change() {
this.isHot = !this.isHot;
}
},
computed: {
info() {
//注意这里的isHot要加this
return this.isHot ? '炎热' : '寒冷';
}
}
})
vm.$watch('isHot', {
immediate: true, //初始化时先调用一次handler
//当isHot被修改时调用handler函数
handler(newVal, oldVal) {
console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
}
})
vm.$watch('info', {
immediate: true, //初始化时先调用一次handler
//当isHot被修改时调用handler函数
handler(newVal, oldVal) {
console.log('info被修改了', '改之前是' + oldVal, '改之后是' + newVal);
}
})
(1)Vue中的watch默认不监测对象内部值的改变(一层,如果监视那么对象地址不变就不变)。
(2)配置deep:true
可以监测对象内部值改变(多层)。
备注:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>深度监视title>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root">
<h2>今天天气很{{info}}h2>
<button @click="change">点击切换天气button>
<hr />
<h3>a的值是:{{numbers.a}}h3>
<button @click="numbers.a++">点击让a+1button>
<hr />
<h3>b的值是:{{numbers.b}}h3>
<button @click="numbers.b++">点击让b+1button>
<h3>强制让numbers变h3>
<button @click="numbers = {a:666, b:999}">点我改变numbersbutton>
div>
<script>
new Vue({
el: "#root",
data: {
isHot: true,
numbers: {
a: 1,
b: 2,
},
},
methods: {
change() {
this.isHot = !this.isHot;
},
},
computed: {
info() {
//注意这里的isHot要加this
return this.isHot ? "炎热" : "寒冷";
},
},
watch: {
isHot: {
// immediate: true, //初始化时先调用一次handler
//当isHot被修改时调用handler函数
handler(newVal, oldVal) {
console.log(
"isHot被修改了",
"改之前是" + oldVal,
"改之后是" + newVal
);
},
},
//监视多级结构中某个属性的变化使用引号包起来(之前不加引号都是简写形式)
"numbers.a": {
handler() {
console.log("a被改变了");
},
},
//如果下面这么写,即使ab变了也不会执行handler,因为这么写意思是监视numbers这个属性
//而这个属性值是一个对象,只要numbers对象的地址不变就不变,除非像上面div写的暴力方法(强制改变)
numbers: {
handler() {
console.log("numbers改变了");
},
},
//但是如果加个deep,就可以监视多级结构中某个属性的变化,ab变了numbers也变
//监视多级结构中所有属性的变化
numbers: {
deep: true,
handler() {
console.log("numbers被改变了");
},
},
},
});
script>
body>
html>
如果监视只需要handler,不需要其他配置项(如immediate、deep等)时,可以进行简写。
形式1:
watch: {
//正常写法
isHot: {
//immediate: true, //初始化时先调用一次handler
//当isHot被修改时调用handler函数
handler(newVal, oldVal) {
console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
}
},
//简写
// 如果handler函数中没有其他的配置项(deep,immediate等),可以简写成以下形式
isHot(newVal, oldVal) {
console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
}
}
形式2:
//复杂形式
vm.$watch('isHot', {
//当isHot被修改时调用handler函数
handler(newVal, oldVal) {
console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
}
})
//简写形式
vm.$watch('isHot',function (newVaule,oldVaule) {
console.log(newVaule,oldVaule);
})
首先我们要知道,这两个属性并不冲突,在开发中可以两个同时使用,这里只是对比一下他们在相同情况下的区别。
对比拼接姓名的案例:
<div id="root">
姓: <input type="text" v-model="firstName"><br><br>
名: <input type="text" v-model="lastName"><br><br>
姓名: <span>{{fullName}}span>
div>
使用计算属性:
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName() {
console.log('fullName被读了');
return this.firstName + '-' + this.lastName;
}
}
})
使用监视属性:
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三',
fullName: '张-三'
},
watch: {
firstName: {
handler(newVal, oldVal) {
this.fullName = newVal + '-' + this.lastName;
}
},
lastName: {
handler(newVal, oldVal) {
this.fullName = this.firstName + '-' + newVal;
}
},
}
})
这么看好像计算属性更简单,那么我们是不是可以都使用计算属性不用监视属性了呢?这里watch有一个优点,它可以异步。
比如我如果手动改了firstName要等一秒钟再改fullName呢?
用监视属性,我们就可以这么写:
watch: {
firstName: {
handler(newVal, oldVal) {
setTimeout(() => {
this.fullName = newVal + '-' + this.lastName;
}, 1000);
}
}
}
总结:
1、computed能完成的功能,watch都可以完成。
2、watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的原则:
1、所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
2、所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数等等),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。