# 计算属性和侦听属性与过滤器

# 计算属性

在开发中经常会涉及到一种需求,一个数据需要通过其他数据计算而来。例如:购物车,平常开发数据与数据关联计算是非常麻烦的,而 Vue 中的计算属性可以轻松帮你解决,可以像绑定普通属性一样在模板中绑定计算属性,可以直接使用{{}}向页面输出。

# 计算属性的基本使用

在实例的 computed 选项中定义你的计算属性,直接使用{{}}向页面输出。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>syl-vue-test</title>
    <!-- 引入 vue.js -->
    <script src="https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js"></script>
  </head>
  <body>
    <div id="app">
      <p>我名字正着写:{{name}}</p>
      <!-- reverseName 计算属性  可以像绑定普通属性一样在模板中绑定计算属性-->
      <p>计算出我名字倒着写:{{reverseName}}</p>
    </div>
    <script>
      var app = new Vue({
        el: "#app",
        data: {
          name: "实验楼",
        },
        computed: {
          //reverseName 是一个计算属性
          reverseName: function () {
            return this.name.split("").reverse().join("");
          },
        },
      });
    </script>
  </body>
</html>
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

运行结果:

此处输入图片的描述

这里我们声明了一个计算属性 reverseName ,你可以像绑定普通属性一样在模板中绑定计算属性。当你的计算属性的依赖数据发生改变时,你的相关计算属性也会重新计算。上面通过计算属性轻松地实现出展示名字倒着写,你也快看看,你名字倒过来是什么!

重点:Vue 中计算属性是 惰性的,只有当依赖数据发生改变时,才会触发计算,否则,它的值是上一次触发计算的缓存值,下面例子很清楚说明,它是惰性的。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>syl-vue-test</title>
    <!-- 引入 vue.js -->
    <script src="https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js"></script>
  </head>
  <body>
    <div id="app">{{now}}</div>
    <script>
      var app = new Vue({
        el: "#app",
        data: {},
        computed: {
          now: function () {
            return Date.now();
          },
        },
      });
    </script>
  </body>
</html>
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

在你的理解中界面会一直时间更新,其实我们定义的 now 并没有和实例中数据建立响应式依赖,只是依赖 Date 对象获取系统时间,它只会计算一次,然后将值缓存,要使得他改变只有 刷新 才能触发,运行结果:

此处输入图片的描述

# 计算属性的 setter 和 getter

计算属性的 setter 和 getter 是它的高级使用,上面的例子只使用到它的 getter 属性,通过其他数据计算而得,其实我们也可以,直接赋值,通过计算属性来改变依赖数据的值。

下面例子我们通过触发计算属性 setter,改变原本关联数据。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>syl-vue-test</title>
    <!-- 引入 vue.js -->
    <script src="https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js"></script>
  </head>
  <body>
    <div id="app">
      <p>firstName:{{firstName}}</p>
      <p>lastName:{{lastName}}</p>
      <p>全名是:{{fullName}}</p>
      <button v-on:click="changeName">改姓</button>
    </div>
    <script>
      var app = new Vue({
        el: "#app",
        data: {
          firstName: "王",
          lastName: "花花",
        },
        methods: {
          //changeName 定义一个方法改变 计算属性 fullName 的值
          changeName: function () {
            //修改计算属性 fullName 等于李花花
            this.fullName = "李花花";
            //上面一句等于触发了 fullName 属性的 setter
          },
        },
        computed: {
          fullName: {
            //getter
            get: function () {
              return this.firstName + this.lastName;
            },
            //setter  直接改变计算属性 fullName的值就可以触发setter this.fullName='XX'
            set: function (newName) {
              var name = newName;
              this.firstName = name.slice(0, 1); //取新值的第一个字符
              this.lastName = name.slice(1); //从新值的第二个字符开始取值
            },
          },
        },
      });
    </script>
  </body>
</html>
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
40
41
42
43
44
45
46
47
48
49
50

运行结果:

此处输入图片的描述

# 侦听属性

在开发我们需要监听数据的变化,Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动,侦听属性。在实例 watch 选项中确定监听项。

下面这个例子,我们通过按钮点击改变 msg 的值,并且监听新旧值改变,并输出新旧值。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>syl-vue-test</title>
    <!-- 引入 vue.js -->
    <script src="https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js"></script>
  </head>
  <body>
    <div id="app">
      <p>{{msg}}</p>
      <!-- v-on:click 简写为 @click -->
      <button @click="handleClick('hello syl')">改变msg</button>
    </div>

    <script>
      var app = new Vue({
        el: "#app",
        data: {
          msg: "hello",
        },
        methods: {
          //改变 msg的值
          handleClick: function (val) {
            this.msg = val;
          },
        },
        // watch 监听属性
        watch: {
          //监听新旧值  监听属性有两个参数,第一个新值,第二个旧值
          msg: function (newVal, oldVal) {
            alert("新值" + newVal + "----" + "旧值" + oldVal);
          },
        },
      });
    </script>
  </body>
</html>
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
40

运行效果:

此处输入图片的描述

# 计算属性与侦听属性对比

计算属性和侦听属性两者在很多场景都是共同,都可以实现同样的需求。

我们使用侦听属性,改写计算属性例子:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>syl-vue-test</title>
    <!-- 引入 vue.js -->
    <script src="https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js"></script>
  </head>
  <body>
    <div id="app">
      <p>firstName:{{firstName}}</p>
      <p>lastName:{{lastName}}</p>
      <p>全名是:{{fullName}}</p>
      <button v-on:click="changeName">改姓</button>
    </div>
    <script>
      var app = new Vue({
        el: "#app",
        data: {
          firstName: "王",
          lastName: "花花",
          fullName: "王花花",
        },
        methods: {
          //changeName 定义一个方法改变  fullName 的值
          changeName: function () {
            this.fullName = "李花花";
          },
        },
        watch: {
          //fullName 侦听属性,监听fullName 值的改变
          fullName: function (val) {
            var name = val;
            this.firstName = name.slice(0, 1);
            this.lastName = name.slice(1);
          },
        },
      });
    </script>
  </body>
</html>
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
40
41
42
43

效果是一样的,运行效果:

此处输入图片的描述

注意: 通常来说能用计算属性代替就用计算属性代替,多大情况计算属性代码更精简,可读性更高。

# 过滤器

计算属性和侦听属性,在数据处理环节中很有作用,但是用它来处理数据过滤不是最优的,在 Vue 中我们有一个专门处理数据过滤的东西:过滤器。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持),用法示例:

<p>{{msg2|getString}}</p>
<p v-bind:class="msg2|getString"></p>
1
2

# 过滤器使用方法

在双花括号插值和 v-bind 表达式中把需要过滤的数据用 | 与过滤器分割 (data|fliter)

例子:

使用 filters 过滤器实现大写转换和自动去除字符串中的数字

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>syl-vue-test</title>
    <!-- 引入 vue.js -->
    <script src="https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- toUpperCase   getString  为自定义的过滤器-->
      <p>小写转换大写:过滤前:{{msg}} 过滤后: {{msg|toUpperCase}}</p>
      <p>去除数字:过滤前:{{msg2}} 过滤后: {{msg2|getString}}</p>
    </div>
    <script>
      var app = new Vue({
        el: "#app",
        data: {
          msg: "hello",
          msg2: "1s2y3l",
        },
        // filters 过滤器选项
        filters: {
          //toUpperCase 定义一个字符串转大写的过滤器
          toUpperCase: function (val) {
            return val.toUpperCase();
          },
          //getString 定义一个获取去除数字的过滤器
          getString: function (val) {
            let newVal = "";
            val.split("").map(function (item) {
              if (9 >= item && item >= 0) {
                return;
              } else {
                return (newVal += item);
              }
            });
            return newVal;
          },
        },
      });
    </script>
  </body>
</html>
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
40
41
42
43
44
45
46

运行结果:

此处输入图片的描述

# 过滤器应用场景

应用比较多的商品价格过滤、表单数据过滤等。

我们从后台获取的数据一般是这样

{ courseName:'xxx', price:199, coupon:8 }
1

我们请求到的数据并没有¥,在开发中直接去操作数据源是不推荐的,此时我们的过滤器就派上用场,定义一个拼接¥的过滤器,只是在视图层面实现了效果。

此处输入图片的描述

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>syl-vue-test</title>
    <!-- 引入 vue.js -->
    <script src="https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- joint  为自定义的过滤器-->
      <p>不要¥899,只要{{price|joint}}</p>
    </div>
    <script>
      var app = new Vue({
        el: "#app",
        data: {
          //后台价格数据
          price: 199,
        },
        // filters 过滤器选项
        filters: {
          //joint 定义¥拼接过滤器
          joint: function (price) {
            return "¥" + price;
          },
        },
      });
    </script>
  </body>
</html>
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

运行结果:

此处输入图片的描述

# 综合小练习

综合计算属性和过滤器,实现一个简易的购物车:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>syl-vue-test</title>
    <!-- 引入 vue.js -->
    <script src="https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js"></script>
    <style>
      * {
        padding: 0;
        margin: 0;
      }
      .price {
        font-size: 22px;
        color: brown;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- joint  为自定义的过滤器-->
      <p>单价<span class="price">{{price|joint}}</span></p>
      数量:<input type="number" v-model="goodsNum" />
      <p>总价:<span class="price">{{allPrice|joint}}</span></p>
    </div>
    <script>
      var app = new Vue({
        el: "#app",
        data: {
          goodsNum: 0,
          price: 199,
        },
        computed: {
          allPrice: function () {
            return this.goodsNum * this.price;
          },
        },
        // filters 过滤器选项
        filters: {
          //joint 定义¥拼接过滤器
          joint: function (price) {
            return "¥" + price;
          },
        },
      });
    </script>
  </body>
</html>
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
40
41
42
43
44
45
46
47
48
49
50

效果:

此处输入图片的描述