初学vue(全家桶)-第3天(vue2):计算属性,监视属性

初学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的指向问题这里就不细说)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值