1 监听器的定义与使用

Vue提供了一种更通用的方式来观察和响应Vue实例上的数据变动:监听属性。当有一些数据需要随着其他数据变化而变动时,就可以使用监听器。 听起来监听器和计算属性差不多,从功能描述来看,确实是,不过在实际应用中二者还是有很大差别。下面来介绍一下监听器的使用,以及和计算属性的区别。

1.1 监听器基本使用

监听器是在Vue实例的选项对象的watch选项中定义的。下面通过监听器来实现千米与米之间的换算。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>01 监听器的定义与使用</title>
</head>
<body>

<div id="app">
    千米:<input type="text" v-model="kilometers">
    米:<input type="text" v-model="meters">
</div>

<script src="vue.js"></script>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            // 初始化为0
            kilometers: 0,
            meters: 0
        },
        // 监听器定义与使用(是以函数的形式,可以最多有两个参数,val oldVal)
        watch: {
            kilometers: function (val) {
                this.meters = val * 1000;
            },
            // 监听器可以接受两个参数,val是当前值,oldVal是改变之前的值
            meters: function (val, oldVal) {
                this.kilometers = val / 1000;
            }
        }
    });
</script>
</body>
</html>

上面编写了两个监听器,分别监听数据属性kolimeters和meters变化,当其中一个数据属性发生改变时,对应的监听器就会被调用,经过计算得到另一个数据属性的值。浏览器中显示的效果如下:
在这里插入图片描述

注意事项:

1、不要使用箭头函数来定义监听器函数,例如下面的代码:

kilometers: (val) => {
            this.kilometers = val;
            this.meters = this.kilometers * 1000;
}

因为箭头函数中的this指向的是最近一层非箭头函数中this的指向,本例中箭头函数外面没有非箭头函数,因此箭头函数中this指向window对象,this.kilometers和this.meters都是undefined。

2、监听器还可以通过Vue实例方法来写(不推荐)。

vm.$watch('kilometers', function (val, oldVal) {
		this.meters = val * 1000;
    });
    vm.$watch('meters', function (val, oldVal) {
        this.kilometers = val / 1000;
    })

1.2 监听器的更多形式

1.2.1 监听器接方法名

监听器在定义时,除了直接写一个函数外,还可以接一个方法名。如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>02 监听器的更多形式-接方法名</title>
</head>
<body>
<div id="app">
    年龄:<input type="text" v-model="age">
    <span v-show="info">{{info}}</span>
</div>

<script src="vue.js"></script>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            // 年龄默认值为0
            age: 0,
            // 空字符串进行布尔判断时是false,所以初始不显示
            info: ''
        },
        methods: {
            checkAge() {
                if (this.age >= 18) {
                    this.info = "已成年";
                } else {
                    this.info = "未成年";
                }
            }
        },
        watch: {
            // 监听器接方法名,但要使用引号引起来
            age: 'checkAge'
        }
    })
</script>
</body>
</html>

在浏览器中运行效果如下:
在这里插入图片描述

1.2.2 监听一个对象属性的变化

监听器还可以监听一个对象的属性变化,代码如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>03 监听器的更多形式-监听一个对象属性的变化</title>
</head>
<body>

<div id="app">
    <!--监听person对象的age属性-->
    年龄:<input type="text" v-model="person.age">
    <!--这里使用v-if也可以-->
    <span v-show="info">{{info}}</span>
</div>

<script src="vue.js"></script>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            person: {
                name: "steve",
                age: 0
            },
            info: ''
        },
        // 正确写法
        watch: {
            person: {
                // handler用于定义当数据变化时调用的监听器函数
                // val是当前值,oldVal是改变之前的值,二者都是person对象
                handler: function (val, oldVal) {
                    if (val.age >= 18) {
                        this.info = "已成年";
                    } else {
                        this.info = "未成年";
                    }
                },
                // 表示只要person对象中的属性发生变化,就调用handler()方法,无论该属性嵌套有多深。
                deep: true,
            }
        }

        // 错误写法
        // watch: {
        //     监听对象时,必须使用handler方法
        //     person: function (val) {
        //         if (val.age >= 18) {
        //             this.info = "已成年";
        //         } else {
        //             this.info = "未成年";
        //         }
        //     }
        // }
    })
</script>
</body>
</html>

在浏览器中运行结果与上面一致。

注意事项:

1、监听器函数在初始渲染时并不会被调用,只有在后续监听的属性发生变化时才会被调用;如果要让监听器函数在监听后立即执行,可以使用immediate选项,设为true。例如:

watch: {
            person: {
                handler: function (val, oldVal) {
                    if (val.age >= 18) {
                        this.info = "已成年";
                    } else {
                        this.info = "未成年";
                    }
                },
                deep: true,
                // 监听器函数在初始渲染时并不会被调用,只有在后续监听的属性发生变化时才会被调用;如果要让监听器函数在监听后立即执行,可以使用immediate选项,设为true。
                immediate: true
            }
        }

当页面加载后,由于age初始值为0,因此会立即显示“未成年”。

2、如果仅仅监听对象的一个属性,且变化也不影响对象的其他属性,那么没必要像上面那么麻烦,直接监听该对象的属性即可,如下所示:

        watch: {
            // 直接监听person对象的age属性
            // person.age中有个特殊字符点号,用单引号括起来即可,单引号不可去除。
            'person.age': function (val) {
                if (val >= 18) {
                    this.info = "已成年";
                } else {
                    this.info = "未成年";
                }
            }
        }

2 监听器与计算属性的区别

2.1 实例

当需要在数据变化时执行异步或开销较大的操作时,使用监听器是最合适的。例如在一个在线问答系统中,用户输入的问题需要到服务端获取答案,就可以考虑对问题属性进行监听,在异步请求答案的过程中,可以设置中间状态,向用户提示“请稍后”,这样的功能使用计算属性就无法做到了。下面使用监听器实现Fibonacci数列计算的例子,斐波那契数列计算比较耗时(递归),采用HTML5新增的WebWorker来计算。将斐波那契数列计算单独放到一个js文件中(Worker线程),页面显示放到html文件中(主线程)。

fibonacci.js(Worker线程)

function fibonacci(n) {
    // arguments为函数内部对象,包含传入函数的所有参数;arguments.callee代表函数名,多用于递归,这里便是递归调用。
    return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);
}

onmessage = function (event) {
    // 事件对象的data属性可以获取主线程发来的数据,radix是“进制”的意思,10表示十进制
    var num = parseInt(event.data, 10);
    // 调用计算斐波那契数列的函数,并传入参数,并将计算结果发送给主线程
    postMessage(fibonacci(num));
}

接下来是主页面(主线程),代码如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>04 监听器结合WebWorker</title>
</head>
<body>
<div id="app">
    请输入要计算斐波那契数列的第几个数:
    <input type="text" v-model="num">
    <p v-show="result">{{result}}</p>
</div>

<script src="vue.js"></script>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            num: 0,
            // 初始值为空字符串,不显示
            result: '',
        },
        watch: {
            num: function (val) {
                this.result = "请稍后...";
                if (val > 0) {
                    // 主线程采用new命令,调用Worker()构造函数(参数是一个脚本文件,该文件就是Worker线程所要执行的任务),新建一个Worker线程。
                    let worker = new Worker("fibonacci.js");
                    // 主线程调用worker.postMessage()方法(方法的参数就是主线程传给Worker线程的数据),向Worker线程发送消息。
                    worker.postMessage(val);
                    // 接着主线程通过worker.onmessage指定监听函数,结束子线程发回来的消息。事件对象的data属性可以获取Worker线程发来的数据。
                    worker.onmessage = (event) => this.result = event.data;
                } else {
                    // 小于等于0的输入显示“不合法”
                    this.result = '不合法的输入';
                }
            }
        }
    });
</script>
</body>
</html>

运行效果:

中间状态:输入斐波那契数列第40个数,程序开始递归运算,这个过程比较耗费时间,故设置一个中间状态“请稍后”。
在这里插入图片描述

最终结果:
在这里插入图片描述

说明:

1、斐波那契数列计算耗时,所以设置了一个中间状态,给用户一个提示。

2、注意主页面中箭头函数的使用,其中this指向的是最近一层非箭头函数中this的指向,即监听器num,此时this指向该Vue实例vm,指向正确。在Vue开发过程中,经常会遇到this指向问题,合理地使用箭头函数可以避免很多问题。

2.2 总结

监听器与计算属性的区别是,监听器不需要返回新的数据,不能被当作数据属性使用,当需要在数据变化时执行异步或开销较大的操作时,使用监听器是最合适的。

Logo

前往低代码交流专区

更多推荐