首页 > 编程笔记

Vue computed(计算属性)详解

当 Vue.js 实例对象的对象属性值发生改变的时候,能自动更新并渲染到 View。但是在实际项目中,Vue.js 实例对象中的数据,会依赖其他数据的改变而改变,View 层需要实时感知到底层数据的变化,从而及时渲染到 View 层并显示。

Vue.js 提供了 computed 和 watch 属性,可以满足这样的项目需求。本节先介绍 computed 的特点、使用方法和注意事项。

computed计算属性

模板内的表达式在使用时非常便利,但是设计它们的初衷是进行简单运算。在模板中放入太多的逻辑会让模板负担过重且难以维护。

以下代码比较难理解和维护:
<div id = "example">
    {{ message.split('').reverse().join('')}}
</div>
在这个地方,模板不再是简单的声明式逻辑。开发人员必须看一段时间才能意识到,这里是想显示变量 message 的翻转字符串。当开发人员在模板中的多处包含此翻转字符串时,就会更加难以处理。

对于类似这样的复杂逻辑,开发人员应使用 computed 属性实现,代码如下:
<div id="app">
    <p>基本属性 message: {{ message }}</p>
    <p>计算属性 reversedMessage: {{ reversedMessage }}</p>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: '#app',
        data: {
            message: 'Hello'
        },
        computed: {
            //计算属性的getter
            reversedMessage: function() {
                // 'this' 指向 vm 实例
                return this.message.split('').reverse().join('');
            }
        }
    });
</script>
在上面的代码中声明了一个计算属性 reversedMessage,并且定义了一个函数,将这个计算属性赋给这个函数,相当于 vm.reversedMessage 计算属性的 getter() 函数。可以直接通过这个函数获取计算属性 reversedMessage 的值。

因为 reversedMessage 计算属性的值是基于 message 属性计算出来的,所以也可以通过改变 message 属性的值,然后重新计算,得到 reversedMessage 的新值,代码如下:
console.log(vm.reversedMessage);  // => 'olleH'
vm.message = 'Goodbye';
console.log(vm.reversedMessage);  // => 'eybdooG'
开发人员可以打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage 的值始终取决于 vm.message 的值,如果单独修改 vm.reversedMessage 的值,控制台会提示计算属性不能设置值,因为没有 setter() 方法。需要说明一下,计算属性默认只有 getter() 方法,而没有 setter() 方法,所以这样直接改变计算属性的值会抛出异常,但是计算属性是可以添加 setter() 方法的。

开发人员可以像绑定普通 property 一样在模板中绑定计算属性。Vue.js 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新,而且最妙的是开发人员已经以声明的方式创建了这种依赖关系:计算属性的 getter() 方法是没有副作用(Side Effect)的,这使它更易于测试和理解。

computed属性的setter()方法

computed 属性默认只有 getter() 方法,开发人员是无法直接修改计算属性的值的。如果开发人员需要直接修改计算属性的值,则可以给计算属性添加 setter() 方法。同时定义 fullName 计算属性的 getter() 和 setter()方法,代码如下:
<div id="app">
    firstName: <input type="text" v-model="firstName"><br/>
    lastName: <input type="text" v-model="lastName"><br/>
    fullName: <input type="text" v-model="fullName"><br/>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: '#app',
        data: {
            firstName: '',
            lastName: ''
        },
        computed: {
            fullName: {
                get: function() {
                    return this.firstName + ' ' + this.lastName;
                },
                set: function(newValue) {
                    let names = newValue.split(' ');
                    this.firstName = names[0];
                    this.lastName = names[1];
                }
            }
        }
    });
</script>
这样就可以在控制台直接给 fullName 计算属性赋予新的值了,如 vm.fullName='zhao liu'。

computed属性同方法的对比

在 Vue.js 中,可以将计算属性绑定到 View 中进行渲染显示,同样可以将一个 Vue.js 实例对象的方法,绑定到 View 中显示方法返回的结果,在页面上都会显示,代码如下:
<div id="app">
    <div>
        <p>计算属性:<br/>
            {{ computedNow }}<br/>
        </p>
        <p>方法:<br/>
            {{ methodNow() }}<br/>
        </p>
    </div>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: '#app',
        methods: {
            methodNow: function() {
                console.log("调用了 methodNow 方法");
                return Date.now();
            }
        },
        computed: {
            computedNow: function() {
                console.log("执行了 computedNow 计算属性");
                return Date.now();
            }
        }
    });
</script>
但是如果在 View 中同时绑定多次计算属性和方法,则虽然它们的显示效果一样,但是查看控制台时会发现计算属性函数中的打印语句只执行了一次,而函数中的打印语句执行了多次(每绑定一次就执行一次),代码如下:
<div id="app">
    <div>
        <p>计算属性:<br/>
            {{ computedNow }}<br/>
            {{ computedNow }}<br/>
            {{ computedNow }}<br/>
        </p>
        <p>方法:<br/>
            {{ methodNow() }}<br/>
            {{ methodNow() }}<br/>
            {{ methodNow() }}<br/>
        </p>
    </div>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: '#app',
        methods: {
            methodNow: function() {
                console.log("调用了 methodNow 方法");
                return Date.now();
            }
        },
        computed: {
            computedNow: function() {
                console.log("执行了 computedNow 计算属性");
                return Date.now();
            }
        }
    });
</script>
其原因是 computed 属性是基于它们的响应式依赖进行缓存的。也就是说,计算属性会将它们计算的值保存在缓存,只有当它们依赖的响应式数据发生改变后,计算属性才会重新计算,否则就直接从缓存中获取值进行渲染,而方法不一样,每次调用都执行一次。

computed 属性适合于计算时性能开销比较大的数据,这样就避免每次渲染的时候都要重新计算,浪费资源。如果开发人员不希望使用缓存,则可改成使用方法。

推荐阅读