一篇文章带你“深入”了解vue的计算属性computed和侦听属器watch

计算属性computed

设计插值表达式的初衷是为了简单运算的。但在模板中放入太多的逻辑会让模板过重、难以维护且难以阅读。例如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

在这个地方,我们很难一眼看出来这个是想要得到反转的字符串,如果我们有十个地方需要用到这种反转字符串,就难以维护,并且臃肿,所以,对于任何复杂逻辑,我们都应当使用计算属性

那么计算属性如何使用呢?

计算属性computed是Vue配置对象中的属性,使用方式如下:

<div id="app">
     <!-- 计算属性的值可以像data数据一样,直接被使用 -->
    <p>原始字符串: "{{ name }}"</p>
  	<p>翻转字符串: "{{ getRverseName }}"</p>
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name:"young monk",
            age: 18
        },
        computed: {
            getRverseName(){// 返回的值,就是计算属性的值
                return this.name.split("").reverse().join("")
            }
        }
    })
</script>

我们可以看到getRverseName的值取决于name,当我们改变name的时候,对应的getRverseName的值也会改变。

不知道你有没有发现,我们使用方法一样也可以实现上面的内容

<div id="app">
    <p>原始字符串: "{{ name }}"</p>
    <p>翻转字符串: "{{ getRverseName() }}"</p>
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk",
            age: 18
        },
        methods: {
            getRverseName() {
                return this.name.split("").reverse().join("")
            }
        }
    })
</script>

我们不难看出,通过执行getRverseName()方法也可以得到相同的结果,那为什么还要有计算属性呢?

虽然我们在插值表达式中调用方法也可以得到相同的效果,但是使用计算属性和使用方法有着本质的区别。

当我们使用方法时,每一次页面重新渲染,对应的方法都会执行一次。而计算属性只有关联的值改变时才会重新执行。

比如说,我们改变age的值(vm.age = 17),页面会重新渲染,这个时候getRverseName()方法会再次执行一次,但是我们并不需要该方法执行,因为改动的数据和这个函数没用任何关系,如果这个函数中有很复杂的逻辑和计算内容,性能开销较打。但我们利用计算属性,就不会出现这种情况。

所以,计算属性和方法的最本质的区别,是:计算属性是基于响应式依赖进行缓存的,计算属性的值一直存于缓存中,只要它依赖的data数据不改变,每次访问计算属性,都会立刻返回缓存的结果,而不是再次执行函数。而方法则是每次触发重新渲染,调用方法将总会再次执行函数。

深入计算属性

计算属性除了写成一个函数之外,还可以写成一个对象,对象内有两个属性,getter&setter,这两个属性皆为函数,写法如下:

const vm = new Vue({
  el: '#app',
  computed: {
    fullName: {
      getter () {
        // ...
      },
      setter () {
        // ...
      }
    }
  }
})

getter 读取

在前面,我们直接将计算属性写成了一个函数,这个函数即为get函数。也就是说,计算属性默认只有getter。当我们去获取某一个计算属性时,就会执行get函数。getterthis,被自动绑定为Vue实例

<div id="app">
    <p>原始字符串: "{{ name }}"</p>
  	<p>翻转字符串: "{{ getRverseName }}"</p>
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name:"young monk",
            age: 18
        },
        computed: {
            getRverseName:{
            	get(){
         			return this.name.split("").reverse().join("")
    			}
        	}
           /*等同于
            getRverseName(){
                return this.name.split("").reverse().join("")
            }
           */
        }
    })
</script>

setter 设置

这是一个可选的选项,set函数会在给计算属性重新赋值时会执行。参数是被重新设置的值。setterthis,被自动绑定为Vue实例。

<div id="app">
    <p>原始字符串: "{{ name }}"</p>
  	<p>翻转字符串: "{{ getRverseName }}"</p>
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name:"young monk",
            age: 18
        },
        computed: {
            getRverseName:{
            	get(){
                    return this.name.split("").reverse().join("")
                },
                set(value){
                    console.log("set执行了,值为"+value)
                    this.name = value
                }
        	}
        }
    })
	vm.getReverseName = "the young monk" //重新赋值
</script>

注意,即使给计算属性赋了值,计算属性也不会重新计算,只有当依赖的响应式属性变化了,计算属性才会重新计算。

侦听器watch

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。我们先来看看怎么使用watch吧。

watch和computed一样,是一个对象,但是watch对象中的写法有很多,键的类型可以是一个正常的key值,也可以是一个字符串。值可以是一个函数、一个字符串、一个对象亦或者可以写成一个数组。我来依次举例,大家就明白了

值的不同写法:

  • 函数类型:可以接收两个参数,第一个参数是被改变的数据(新数据),第二个参数是赋值之前的数据(旧数据)
<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        },
        watch: {
            name(newValue, oldValue) {
                console.log(`新值:${newValue},旧值:${oldValue}`)
            }
        },
    })
    vm.name = "monk"  //更改name的值   新值:monk,旧值:young monk
</script>
  • 字符串类型:值为方法名字,被侦听的数据改变时,会执行该方法。
<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        },
        watch: {
            name:"nameChange"
        },
        methods: {
            nameChange(newValue, oldValue) {
                console.log(`新值:${newValue},旧值:${oldValue}`)
            }
        },
    })
    vm.name = "monk"
</script>
  • 对象类型:写成对象类型时,可以提供选项。
    • handler:必需。handler时被侦听的数据改变时执行的回调函数。handler的值类型为函数/字符串,写成字符串时为一个方法的名字。
    • deep:在默认情况下,侦听器侦听对象只侦听引用的变化,只有在给对象赋值时它才能被监听到。所以需要使用deep选项,让其可以发现对象内部值的变化,将deep的值设置为true,那么无论该对象被嵌套的有多深,都会被侦听到。注意:当对象的属性较多的时候,性能开销会比较大,此时可以监听对象的某个属性
    • immediate:默认值是false,加上immediate选项后,回调将会在侦听开始之后立刻被调用。而不是等待侦听的数据更改后才会调用,也就是说,当添加监听时就会被调用一次
<div id="app">
    {{ info.name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            info: {
                name:"young monk"
            }
        },
        watch: {
            info: {
                handler() {
                    console.log("name的值被改变了")
                },
                deep:true,
                immediate:true
            }
        }
    })
    vm.info.name = "monk"
   /*会执行两次,第一次是immediate的原因,第二次才是因为值的改变执行的
   	 name的值被改变了
     name的值被改变了
   */
</script>
  • 数组类型:可以将多种不同值类型写在一个数组中。感觉花里胡哨,毫无作用
<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        },
        watch: {
            name:[
                "nameChange",
                function(){
                    console.log("函数的方式执行了")
                },
                {
                    handler(){
                        console.log("对象的方式也执行了!")
                    },
                    deep:true,
                    immediate:true
                }
            ]
        },
        methods: {
            nameChange() {
                console.log("方法的方式也执行了")
            }
        }
    })
    vm.name = "monk"
</script>

键的不同写法:

  • 正常对象key值

以上演示的都是正常的对象key值,这里不再赘述。

  • 字符串类型key值:当key值类型为字符串时,可以实现监听对象当中的某一个属性,如:
<div id="app">
    {{ info.name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            info:{
                name: "young monk"
            }
        },
        watch: {
            'info.name'(){
                console.log("对象的值改变啦")
            }
        }
    })
    vm.info.name = "monk"
    // 此时回调函数会执行,控制台中打印出 ` 对象的值改变啦 `
</script> 

vm.$watch()

Vue实例将会在实例化时调用$watch,遍历watch对象的每一个属性。我们也可以利用vm.$watch来实现侦听,用法与watch选项部分一致,略有不同。

  • 侦听某个数据的变化,有两种方式三个参数和两个参数

1、三个参数,一参为被侦听的数据;二参为数据改变时执行的回调函数;三参可选,为设置的选项对象

<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        }
    })
    vm.$watch(
        'name',
        function () {
            console.log("name的值改变啦")
        }, 
        {
            deep: false,
            immediate: true
        })
</script>

2、二个参数,一参为被侦听的数据;二参为选项对象,其中handler属性为必需,是数据改变时执行的回调函数,其他属性可选。

<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        }
    })
    vm.$watch(
        'name',
        {
            handler(){
                console.log("name值改变啦")
            },
            deep:false,
            immediate:true
        })
</script>
  • 侦听某个对象属性的变化
vm.$watch('obj.name', /**参数和上面一样*/)

当监听的数据的在初始不确定,由多个数据得到时,此时可以将第一个参数写成函数类型

vm.$watch(function () {
  // 表达式`this.firstName + this.lastName`每次得出一个不同的结果时该函数都会被调用
  // 这就像监听一个未被定义的计算属性
  return this.firstName + this.lastName;
}, /**参数和上面一样*/)
  • 侦听器函数执行后,会返回一个取消侦听函数,用来停止触发回调
const unwatch = vm.$watch('name', function () {});
unwatch(); // 执行后会取消侦听name数据

使用unwatch时,需要注意的是,在带有immediate选项时,不能在第一次回调时取消侦听数据。如果仍然希望在回调内部用一个取消侦听的函数,那么可以先检查该函数的可用性:

var unwatch = vm.$watch('msg', function () {
    // ...
    //unwatch  直接使用会报错
    if(unwatch) {
      unwatch();  
    }
  },{
    immediate: true
  }
})

侦听器 vs 计算属性

  1. 两者都可以观察和响应Vue实例上的数据的变动。侦听属性可以监听(data和computed)上的数据变化,因为computed的属性也会挂到data上

  2. watch擅长处理的场景是:一个数据影响多个数据。计算属性擅长处理的场景是:多个数据影响一个数据。

  3. 在侦听器中可以执行异步,但是在计算属性中不可以

发布了33 篇原创文章 · 获赞 105 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Newbie___/article/details/105224887