初学Vue
1、计算属性computed
1.1 看如下示例
下面示例中,在两个输入框中输入值时,使得全名那一行的内容自动跟着改变,并且输入框中又默认值。
使用两种方式实现:
- 插值语法实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
姓:<input type="text" v-model:value="firstName">
<br><br>
名:<input type="text" v-model:value="lastName">
<br><br>
全名:<span>{{firstName}}{{lastName}}</span>
</div>
<script>
new Vue({
el:"#root",
data:{
firstName:"张",
lastName:"三",
}
});
</script>
</body>
</html>
使用到了双向绑定v-model,实现数据实时更新,缺点是如果插值中表达式太长,就会显得不那么好阅读,例如:{{firstName.slice(0,3)}}{{lastName.slice(0,4}},这串表达式分别截取了姓和名的前3个字符和前4个字符,想想,如果还有其他要求呢,那么这个表达式必定会越来越长…由此,可以将表达式写在methods的方法中。
- methods方法实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
姓:<input type="text" v-model:value="firstName">
<br><br>
名:<input type="text" v-model:value="lastName">
<br><br>
全名:<span>{{getFullName()}}</span>
</div>
<script>
new Vue({
el:"#root",
data:{
firstName:"张",
lastName:"三",
},
methods:{
getFullName(){
return this.firstName+this.lastName;
}
}
});
</script>
</body>
</html>
将获取全名的过程全部写在方法里面,使得模板中的更加简单明了,并且getFullName方法可以重复利用。
但这样做也有个缺点,就是每次在输入框中输入内容时,就会使得vue对象每次都会对模板中的内容进行重新解析,而模板中methods配置项中有方法,在解析过程中这个方法又会被调用,因此才能获取到输入框中的内容,这样就会影响性能。
1.2 简介
<body>
<div id="root">
姓:<input type="text" v-model:value="firstName">
<br><br>
名:<input type="text" v-model:value="lastName">
<br><br>
全名:<span>{{fullName}}</span>
</div>
<script>
const vm = new Vue({
el:"#root",
data:{
firstName:"张",
lastName:"三",
},
// 计算属性配置项
computed:{
// 计算属性
fullName:{
// get的作用:当读取到fullName时,get会被调用,并且其返回值作为计算属性的结果
get(){
console.log("get被调用了");
return this.firstName + this.lastName;
// get中的this永远指向vue对象,除非改为箭头函数时就会指向window对象
}
}
}
});
</script>
</body>
- 1、定义
要用的属性不存在,通过已有属性计算加工出来的属性就是计算属性。在Vue对象中添加computed配置项,里面放置计算属性。
- 2、底层原理
借助了Object.defineProperty()方法提供的getter和setter方法。
- 3、计算属性中有缓存机制,这就比methods方式效果更高。
计算属性内部的缓存机制,计算出来的数据可以重复用,效率更高,调试方便(计算属性可以在控制台显示)
例如:当模板中多处用到了同一个计算属性时
<div id="root">
姓:<input type="text" v-model:value="firstName">
<br><br>
名:<input type="text" v-model:value="lastName">
<br><br>
全名:<span>{{fullName}}</span>
全名:<span>{{fullName}}</span>
全名:<span>{{fullName}}</span>
全名:<span>{{fullName}}</span>
</div>
此时get方法只会被调用一次,因为缓存中存储了fullname中的值,届时只要用缓存中的值即可。
- 4、get函数的执行时间
- (1)初次读取时会执行一次
- (2)依赖的数据发生改变时会被调用执行(依赖的数据是指计算属性用到的属性的值)
get函数在依赖属性做出改变时也会被调用,这样就可以做到及时更改缓存中的值。
- 5、vm._data也就是data中不会存储计算属性。
例如,在浏览器控制台中做如下操作,可以发现data中并不会存储计算属性。
- 6、计算属性最终会出现在vm上,可以直接读取
借助开发者工具可以看到,这个computed配置项中的计算属性会显示出来的
并且在浏览器控制台中可以发现vm对象上面就有fullName这个计算属性。
为什么计算属性会出现在vm对象上?这就是get函数的作用了。如图:
-
get函数中的this在使用普通函数时指向vm对象,使用箭头函数时会指向window对象。
-
当计算属性要被修改时,必须使用set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
如果没有设置set函数就去修改计算属性,就会报如下错误
设置set属性后,就可以对计算属性进行修改了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
姓:<input type="text" v-model:value="firstName">
<br><br>
名:<input type="text" v-model:value="lastName">
<br><br>
全名:<span>{{fullName}}</span>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
firstName:"张",
lastName:"三",
},
// 计算属性配置项
computed:{
// 计算属性
fullName:{
// get的作用:当读取到fullName时,get会被调用,并且其返回值作为计算属性的结果
get(){
console.log("get被调用了");
return this.firstName +"-"+ this.lastName
},
// set的调用时间为fullName被修改时,并且在set中要引起计算时依赖的数据发生改变
set(value){
// 因为姓和名是两个属性,为了好理解,这里加上"-"分隔开
// 将获取到的姓名放到数组中,用"-"分开
const arr = value.split('-');
// 计算属性必须通过组成他的属性才能算出来,
//因此,在修改计算属性时,必须要修改构成它的原本属性中的值
this.firstName = arr[0];
this.lastName = arr[1];
}
}
}
});
</script>
</body>
</html>
- 计算属性简写
前提:不需要使用set函数时才可以简写
<body>
<div id="root">
姓:<input type="text" v-model:value="firstName">
<br><br>
名:<input type="text" v-model:value="lastName">
<br><br>
全名:<span>{{fullName}}</span>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
firstName:"张",
lastName:"三",
},
// 计算属性配置项
computed:{
// // 计算属性
// fullName:{
// // get的作用:当读取到fullName时,get会被调用,并且其返回值作为计算属性的结果
// get(){
// console.log("get被调用了");
// return this.firstName + this.lastName
// },
// }
// 简写,等同于上面的代码
fullName(){
console.log("get被调用了");
return this.firstName+this.lastName
}
}
});
</script>
</body>
2、监视属性watch
2.1简介
2.1.1 定义
监视对象,用于响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。主要用到了watch
这个配置项
2.1.2 使用要求
监视的属性必须存在才能监视。
2.1.3 写法
写法一:new Vue时传入watch配置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
isHot:true
},
// 用于计算属性
computed:{
// 下面是计算属性简写形式
info(){
return this.isHot?"炎热":"凉爽";
}
},
methods:{
changeWeather(){
this.isHot = !this.isHot;
}
},
// 用watch监视属性
watch:{
// 监视isHot对象中的值发生改变,那么触发事件
isHot:{
// 当isHot发生改变时调用handler函数
handler(newValue,oldValue){
console.log("isHot被修改了",newValue,oldValue);
},
// immediate属性用于页面刷新时就调用handler函数,默认值为false
immediate:true
// ...其他属性
}
}
})
</script>
</body>
</html>
第一行undefined是因为初始化时并没有旧值,所以结果默认为undefined
写法二:通过vm.$watch配置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
isHot: true
},
// 计算属性
computed: {
// 下面是计算属性简写形式
info() {
return this.isHot ? "炎热" : "凉爽";
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
})
vm.$watch("isHot", {
// 传入配置对象
// 当isHot发生改变时调用handler函数
handler(newValue, oldValue) {
console.log("isHot被修改了", newValue, oldValue);
},
// 其他属性:immediate用于页面刷新时就调用handler函数,默认值为false
immediate: true
})
</script>
</body>
</html>
2.2 深度监视
- 特点
(1)Vue中的watch默认不检测对象内部值的改变(一层)
(2)配置deep:true可以检测对象内部值的改变(多层)
(1)Vue自身可以检测对象内部值的改变,但Vue提供的watch配置项默认不可以
(2)使用watch时根据数据的具体结构,决定是否可以采用深度监视
- 深度监视使用示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>深度监视</title>
</head>
<body>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
<!-- 这里直接将事件写在标签中 -->
<h2>a的值是:{{numbers.a}}</h2>
<button @click="numbers.a++">点我a+1</button>
<h2>a的值是:{{numbers.b}}</h2>
<button @click="numbers.b++">点我b+1</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
isHot: true,
numbers:{
a:1,
b:2
}
},
computed: {
info() {
return this.isHot ? "炎热" : "凉爽";
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
},
},
// 监视属性
watch: {
//'isHot'是isHot原始状态,isHot是'isHot'的简写形式
isHot: {
handler(newValue, oldValue) {
console.log("isHot被修改了", newValue, oldValue);
},
immediate: true
},
// =====================================
// 监视多级结构中某个属性的变化
// numbers.a不能被成功监视,只有写成原始状态才能成功被监视
// 'numbers.a':{
// handler(){
// console.log("a被改变了");
// }
// }
// 监视numbers中的b也是上面这种写法
// =====================================
// 监视多级结构中所有属性的变化
numbers:{
deep:true, // 开启深度监视
handler(){
console.log("numbers中的数据改变了");
}
}
}
})
</script>
</body>
</html>
监视多级结构中单个属性的改变,结果:
监视多级结构中所有属性的改变,结果:
2.3 监视简写形式
简写前提是:watch中只用到了handler函数。
<body>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
isHot: true
},
// 计算属性
computed: {
// 下面是计算属性简写形式
info() {
return this.isHot ? "炎热" : "凉爽";
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
watch: {
// 监视正常写法
// isHot:{
// handler(newValue,oldValue){
// console.log("isHot被修改了",newValue,oldValue);
// },
// }
// 监视简写
isHot(newValue, oldValue) {
console.log("isHot被修改了", newValue, oldValue);
}
}
});
</script>
</body>
3、对比watch和computed
- 区别:
1、computed能完成的功能watch都可以完成
2、watch能完成的功能,computed不一定能完成,例如watch可以进行异步操作
(1)示例一:拿计算属性中的姓名实例分别使用watch实现和computed实现来做对比,发现computed能完成的功能watch都可以完成
1、computed实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>computed实现</title>
</head>
<body>
<div id="root">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
姓名:<span>{{fullName}}</span>
</div>
<script>
const vm = new Vue({
el:"#root",
data:{
firstName:"张",
lastName:"三"
},
computed:{
// 计算属性简写
fullName(){
return this.firstName + "-" + this.lastName
}
},
});
</script>
</body>
</html>
可以发现全名属性是通过计算属性计算出来的,并不是data中的数据
2、watch实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>watch实现</title>
</head>
<body>
<div id="root">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
姓名:<span>{{fullName}}</span>
</div>
<script>
new Vue({
el:"#root",
data:{
firstName:"张",
lastName:"三",
fullName:"张-三"
},
watch:{
// 监视姓
firstName(newValue){
this.fullName=newValue + "-" + this.lastName;
},
// 监视名
lastName(newValue){
this.fullName=this.firstName + newValue;
}
}
})
</script>
</body>
</html>
watch实现中,可以看到fullName属性是写在data中的,并且可以通过this(指向vm对象)来访问。
这样对比下来,computed实现要比watch简单多了。watch的代码是命令式且重复的,繁琐多了。
示例二:watch能完成的功能,computed不一定能完成,例如watch可以进行异步操作,而computed不能完成
(这里使用定时器来演示异步操作:当输入框中输入内容后,结果晚一秒输出)
1、computed不可以实现异步操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>computed实现</title>
</head>
<body>
<div id="root">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
姓名:<span>{{fullName}}</span>
</div>
<script>
const vm = new Vue({
el:"#root",
data:{
firstName:"张",
lastName:"三"
},
computed:{
// 计算属性简写
fullName(){
setTimeout(()=>{
return this.firstName + "-" + this.lastName
},1000);
}
},
});
</script>
</body>
</html>
结果:
**分析:
setTimeout方法执行的回调并不受到vue对象的控制,因此在其中使用return返回数值时并不会返回给vm示例,而是交给了执行setTimeout方法的对象,所以这里没得输出。
此外定时器中必须使用箭头函数,如果使用普通函数形式,那么this就会指向window对象,而使用箭头函数时this指向vue对象。
**
2、watch可以实现异步操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../JS/vue.js"></script>
<title>watch实现</title>
</head>
<body>
<div id="root">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
姓名:<span>{{fullName}}</span>
</div>
<script>
new Vue({
el:"#root",
data:{
firstName:"张",
lastName:"三",
fullName:"张-三"
},
watch:{
// 监视姓
firstName(newValue){
// 让姓在修改后延时一秒输出
setTimeout(()=>{
this.fullName=newValue + "-" + this.lastName;
},1000);
},
// 监视名
lastName(newValue){
this.fullName=this.firstName + newValue;
}
}
})
</script>
</body>
</html>
分析:
定时器中是使用箭头函数实现的,this指向vm对象,所以直接修改了vm示例中data配置项中的fullName属性
补充:
(1)所有被Vue管理的函数最好写成普通函数的形式,这样this始终指向vm实例或者组件实例对象
(2)所有不被Vue管理的函数(例如定时器函数,ajax回调函数,Promise的回调函数),最好写成箭头函数的形式,这样this的指向才是vm或者组件实例对象(this的指向问题这里就不细说)