跟着官档学 Vue.js (五) - 计算属性

在之前的章节里面,我们有说过,插值的时候要使用单一的JavaScript表达式,类似于这样子:

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

但假设我们需要在很多地方都使用到这样的一个转换的话,代码看起来就会很臃肿:

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

重复?!纳尼?!我们决不允许这种事情发生,于是乎,计算属性(Computed Properties) 就出现了。

官方例子

<div id="example">
    <p>Original message: "{{ message }}"</p>
    <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
<script>
    var vm = new Vue({
        el: '#example',
        data: {
            message: 'Hello'
        },
        computed: {
            // a computed getter
            reversedMessage: function () {
                // `this` points to the vm instance
                return this.message.split('').reverse().join('');
            }
        }
    });
</script>

上面的代码在浏览器中输出的结果是:

好了,现在我们回头来看一下代码,我们会发现,计算属性数据对象 的区别就是在构建Vue实例的时候,一个放在了 computed 中,一个放在了 data 中,而且,计算属性一般都是一个 函数

虽然计算属性是一个 函数,但在使用计算属性插值的时候,不需要为其添加函数括号:

<!--错误的写法-->
<p>Computed reversed message: "{{ reversedMessage() }}"</p>

仔细看一眼上文的 reversedMessage 函数,我们会发现其中有 this.message,我们轻而易举地可以推断出 this.message == vm.message,我在控制台测试过了,确实是 true

根据 Vue 的特性,我们如果在控制台改变上文中 vm.message 的值,还会发现,vm.reversedMessage 也会跟着改变。毕竟人家叫做响应式嘛。

那如果我手动去更改 reversedMessage 的值呢。

为了方便学习,我把代码改成了下面的样子

<div id="example">
    <p>Original message: "{{ message }}"</p>
    <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
<script>
    var vm = new Vue({
        el: '#example',
        data: {
            message: 'Hello'
        },
        computed: {
            reversedMessage: function () {
                return this.message.split('').reverse().join('');
            }
        },
        updated: function(){
            console.log("updated!");
        }
    });
</script>

在浏览器控制台中输入 vm.message = '12345'vm.reversedMessage = 'abcde' 的结果如下:

好了,测试完毕,发现直接修改 vm.reversedMessage 并不会触发 updated 事件,也不会更改它本身的值,更不会更改 vm.message 的值。

其实,在官方文档中已经说了,我们定义在computed中的函数将用作计算属性 vm.reversedMessagegetter

gettersetter 是 javascript 语言内的一个概念,在这里不扩展。

也就是说,我们只能够通过计算属性来 。然而,我们也可以自己写一个 setter 来让计算属性拥有 的能力。

var vm = new Vue({
    el: '#example',
    data: {
        message: 'Hello'
    },
    computed: {
        reversedMessage: {
            get: function(){
                return this.message.split('').reverse().join('');
            },
            set: function(val){
                this.message = val.split('').reverse().join('');
            }

        }
    },
    updated: function(){
        console.log("updated!");
    }
});

我把代码改成了上面的样子,再在浏览器中去更改 vm.reversedMessage 的值:

因为在代码中我们更改了 message 的值,所以会触发 updated 事件。

methods

在官方文档里面,好像还没有正式地提及 methods,我们第一次见到 methods 是在第一章里面的处理用户输入
我们当时自己总结的结论是: 按照Vue套路行事,数据放data,方法放methods。

那么现在问题就来了,我们在这一章讲的 计算属性里面,也是通过函数方法来实现它的效果的。辣魔,他们之间到底有什么区别呢?

如果我们使用 methods 来实现上面的效果,代码如下:

<div id="example">
    <p>Original message: "{{ message }}"</p>
    <p>Computed reversed message: "{{ reversedMessage() }}"</p>
</div>
<script>
var vm = new Vue({
    el: '#example',
    data: {
        message: 'Hello'
    },
    methods: {
        reversedMessage: function(){
                return this.message.split('').reverse().join('');
        }
    },
    updated: function(){
        console.log("updated!");
    }
});
</script>

注意,使用 methods 进行插值的时候,和 computed 的区别就在于,它需要在函数名后面书写 函数括号

可以看到,浏览器输出的效果是一毛一样的。那么区别到底在哪里呢?

其实很好理解,官方文档中也说得很清楚,计算属性会对计算出来的结果进行缓存,也就是当上文中 message 没有发生改变的时候,多次调用 vm.reversedMessage 这个计算属性时,这个计算属性的运算过程只发生了一次,之后读取的都是缓存。

而调用 vm.reversedMessage() 这个方法时,每一次调用就会执行一次这个运算过程。这样的话开销就会比较大。

watch

watchcomputedmethods 一样,是一个层级的东西,它的作用就如同它的名字一样:监视,放在 watch 中的对象,一旦发生改变就会执行相应的操作,我们上面的例子一样可以用 watch 来改写。

<div id="example">
    <p>Original message: "{{ message }}"</p>
    <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
<script>
var vm = new Vue({
    el: '#example',
    data: {
        message: 'Hello',
        reversedMessage: ''
    },
    watch: {
        message: function(val){
                this.reversedMessage = val.split('').reverse().join('');
        }
    },
    updated: function(){
        console.log("updated!");
    }
});
</script>

watch 中的对象需要在 data 中已经定义了。在发生 updated 的时候才会触发 watch。也就是上面的代码中,reversedMessage 一开始被渲染出来的时候是空值。

官方的文档中只是说明了使用 computed 相对于 watch 来说更方便更简洁。关于缓存的问题在 watch 上没做说明。take it easy,咱们试试不就知道了吗~

<div id="example">

<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>

</div>
<script>
var vm = new Vue({

el: '#example',
data: {
    message: 'Hello',
    reversedMessage: ''
},
watch: {
    message: function(val){
            console.log("hi");
            this.reversedMessage = val.split('').reverse().join('');
    }
},
updated: function(){
    console.log("updated!");
}

});
</script>

在浏览器控制台修改 vm.message 的结果如下:

也就是,watch 也会对计算结果进行缓存。

既然这样子的话,那 computewatch 不就并没有太大区别了吗。

官方文档中指出的常规套路是:在处理这些数据变化时,如果开销很大,或者是需要异步操作,那通常推荐使用 watch 来响应数据的变化。

因为 watch 可以在处理到一半的时候,为数据设置中间状态,而 computed 没办法这么做。

在我看来咧,计算属性倾向于格式化/处理当前的数据,而 watch 倾向于执行数据变化需要进行的操作

文档中的最后一个例子代码无需看懂啦,只需要了解 watch 能造成的作用就行啦~

  none