跳到主要内容位置

Vue 计算属性和方法的区别

张旭乾

到现在,我们接触了 computed 计算属性methods 方法watchers 监听器,这三者使用起来好像没有什么区别,都能响应数据的变化,那么这三者之间该怎们进行选择呢?计算属性、方法和监听器这三种方式是有一定的区别的,但有时候也并不严格区分,接下来的几节课我们看一下它们的区别,之后你就会自己决定什么时候使用哪一个了,这些课的内容如果觉得听不太懂也没关系,后面你会见到它们更多的用法,在以后使用 Vue 的过程中,你也能慢慢的理解了。

Computed 计算属性和 Methods 方法的区别

我们看一下计算属性和方法的区别,在这之前,看一下它们的相同点。在讲计算属性的时候,我们说过,它适合根据 data 里的属性作一些稍微复杂的计算,例如数组过滤,筛选等,并且需要复用的情况:

<ul>
<li v-for="vueBlog in vueBlogs">{{ vueBlog }}</li>
</ul>

data() {
return {
blogPosts: [ "Vue 3.0x 入门实战", "Vue 3.x 完全指南", "React 18 新特性介绍", "JavaScript 基础语法概览", ],
};
},
computed: {
vueBlogs() {
return this.blogPosts.filter((blog) => blog.includes("Vue"));
},
}

而在讲方法的时候,我们重点提到它适合用于事件的处理上边,其实它也可以像计算属性一样,在 html 模板中使用,就是需要加上小括号来调用:

<ul>
<li v-for="vueBlog in getVueBlogs()">{{ vueBlog }}</li>
</ul>

methods: {
getVueBlogs() {
return this.blogPosts.filter((blog) =>blog.includes("Vue"));
},
},

这样看起来没太大区别,不过在实际使用时,计算属性会把计算结果缓存起来,在多次使用计算属性的时候,或者在组件刷新的时候,如果计算属性中使用的 data 属性没有变化,Vue 会直接从缓存中把计算属性的结果拿出来:

<ul>
<li v-for="vueBlog in vueBlogs">{{ vueBlog }}</li>
</ul>
<!-- 按钮点击的时候,counter 属性发生了变化,但是 blogPosts 没发生变化 -->
<!-- 所以 vueBlogs 这个计算属性函数不会重新调用 -->
<button @click="counter += 1">计数器 {{ counter }}</button>

data() {
return {
blogPosts: [ "Vue 3.0x 入门实战", "Vue 3.x 完全指南", "React18 新特性介绍", "JavaScript 基础语法概览", ],
counter: 0,
};
},
computed: {
vueBlogs() {
// 多次点击按钮只会调用一次 vueBlogs()
console.log("调用了 vueBlogs() 计算属性函数");
return this.blogPosts.filter((blog) => blog.includes("Vue"));
},
},

方法则不会缓存计算结果,在 html 中使用的任何属性变化时,方法都会重新执行,对于处理大量数据的情况下,效率会比较低下:

<ul>
<li v-for="vueBlog in getVueBlogs()">{{ vueBlog }}</li>
</ul>
<button @click="counter += 1">计数器 {{ counter }}</button>

methods: {
getVueBlogs() {
// 每次点击按钮都会执行
console.log("调用了 vueBlogs() 方法");
return this.blogPosts.filter((blog) => blog.includes("Vue"));
},
},

对于计算属性,只有当它依赖的 data 属性值发生变化时,才会重新计算:

<ul>
<li v-for="vueBlog in vueBlogs">{{ vueBlog }}</li>
</ul>
<!-- 改成添加一篇文章,每次 blogPost 都会变化,vueBlogs() 计算属性函数也会多次调用 -->
<button @click="blogPosts.push('Vue 3.x 计算属性和方法的区别')">计数器 {{ counter }}</button>

computed: {
vueBlogs() {
// 每次点击按钮都会调用 vueBlogs()
console.log("调用了 vueBlogs() 计算属性函数");
return this.blogPosts.filter((blog) => blog.includes("Vue"));
},
},

如果明确需要不缓存数据,那么使用方法比较合适,一般情况下还是需要缓存结果来提升性能,那么就使用计算属性。 另外,在 html 模板中使用方法时,可以给方法传递参数:

<li v-for="vueBlog in getVueBlogs(1)">{{ vueBlog }}</li> 

methods: {
getVueBlogs(count) {
return this.blogPosts
.filter((blog) => blog.includes("Vue"))
.slice(0, count);
},
}

方法也可以像普通 JavaScript 函数中那样,用来抽离公共的业务逻辑,方便在计算属性或 watcher 监听器中使用。这种情况下,在计算属性中调用方法,它的结果仍然是缓存起来的:

computed: {
vueBlogs() {
return this.getVueBlogs(20); // 使用 this 访问 methods 中定义的方法
},
},

示例

我们来实际看一下运行示例,实际感受一下它们之间的区别。在 index.js 中,我们从 data() 函数里边返回一个包含 blogPosts 属性的对象,它的值是一个数组,保存了几个示例的博客文章标题:

data() {
return {
blogPosts: [
"Vue 3.0x 入门实战",
"Vue 3.x 完全指南",
"React 18 新特性介绍",
"JavaScript 基础语法概览",
],
};
},

现在,我想显示标题里只包含 Vue 的文章,那么可以定义一个计算属性,在里边过滤数组元素,并返回一个新的数组,我们在 computed 里定义一个 vueBlogs() 计算属性,在里边调用 blogPosts 数组的 filter() 方法进行过滤:

computed: {
vueBlogs() {
return this.blogPosts.filter((blog) => blog.includes("Vue"));
},
},

之后在 html 模板中展示一下,使用 v-for 指令,直接遍历 vueBlogs 这个计算属性:

<ul>
<li v-for="vueBlog in vueBlogs">{{ vueBlog }}</li>
</ul>

运行一下项目,可以看到只有包含 vue 的博客标题显示出来了。 那么,我们可以用同样的方式,使用 methods 展示同样的效果,在 methods 里边,定义一个 getVueBlogs()方法,使用同样的代码过滤博客列表:

  methods: {
getVueBlogs() {
return this.blogPosts.filter((blog) => blog.includes("Vue"));
}
},

之后在 html 中,需要把 v-for 里边的 vueBlogs 计算属性,改成对 getVueBlogs() 方法的调用,需要加上小括号:

<li v-for="vueBlog in getVueBlogs()">{{ vueBlog }}</li>

这样看一下运行结果,可以看到还是展示了同样的效果。为了展示它们两个的区别,也就是缓存和不缓存的区别,我们可以再在 data 新添加一个 counter 属性,这个属性就是随机加的,目的单纯的就是为了演示计算属性和方法的区别:

data() {
return {
blogPosts: [],
counter: 0,
};
},

之后在 html 模板中,添加一个 button 按钮元素,在它的文本标签里,使用 counter 这个属性,然后给它注册一个点击事件,点击的时候把 counter 进行加 1 操作:

<button @click="counter += 1">计数器 {{ counter }}</button>

然后为了看到效果,在 computed 和 methods 中打印一些字符串,这样能清楚的看到这些函数是不是调用了:

// computed
vueBlogs() {
console.log("调用了 vueBlogs() 计算属性函数");
return this.blogPosts.filter((blog) => blog.includes("Vue"));
}

// methods
getVueBlogs() {
console.log("调用了 vueBlogs() 方法");
return this.blogPosts.filter((blog) => blog.includes("Vue"));
},

好了,现在我们的 html 中使用的是 getVueBlogs() 方法,我们先看一下它是怎么执行的,刷新一下页面,可以看到 getVueBlogs() 里的 console.log() 先打印了一下『调用了 vueBlogs() 方法』,之后我们点击一下按钮,可以看到计数器加了 1,而 getVueBlogs() 又执行了一次,再多点几次按钮,可以看到每次点击按钮的时候,getVueBlogs() 都会执行一次。【解释一下为什么】 我们再把 html 中的代码改回使用 vueBlogs 计算属性:

<li v-for="vueBlog in vueBlogs">{{ vueBlog }}</li>

这个时候,刷新一下页面,可以看到 vueBlogs 里的 console.log 执行了一次,打印『调用了 vueBlogs() 计算属性函数』,之后点击按钮,可以看到 console.log()就没有执行了,证明现在页面上显示的是缓存中的计算结果,因为 counter 变化了而 blogPosts 没有变化,所有 vueBlogs 这个计算属性的函数就没有重新执行。 使用 methods 方法的场景,就是禁止缓存的时候,或者需要传递参数的情况。例如假设在过滤标题含有 vue 的博客列表的基础上,再限制只显示固定数量的博客标题,那么我们可以让 getVueBlogs() 这个方法,接收一个参数,count,然后在调用 filter() 之后,再调用一次 slice(),截取指定长度的元素:

getVueBlogs(count) {
console.log("调用了 vueBlogs() 方法");
return this.blogPosts
.filter((blog) => blog.includes("Vue"))
.slice(0, count);
},

那么在 html 模板中,我们可以给 getVueBlogs 传递参数,例如 1:

<li v-for="vueBlog in getVueBlogs(1)">{{ vueBlog }}</li>

这个时候再看一下结果,页面上就只显示了一个符合条件的博客标题了。

小结

好了,这个就是 computed 计算属性和 methods 方法之间的区别:

  • computed 计算属性适合根据 data 里的属性,来做一些简单的计算并返回结果,例如数组的排序、筛选等等,它的结果会缓存起来,只有使用到的 data 中的属性发生变化时才会重新计算,其它情况会直接返回计算结果,以提高效率。计算属性可以像 data 属性一样直接在 html 模板中使用。
  • methods 适合做一些更复杂的计算,通常是作为事件处理函数使用,或者是抽取公用的业务逻辑,当作普通的 JavaScript 函数一样使用。
提示

《Vue 3.x 全家桶完全指南与实战》课程已上线,包括 Vue 3.x、TypeScript、Vue Router 4.x、Vuex 4.x 所有初级到高级的语法特性详解,让你完全胜任 Vue 前端开发的工作。点击查看详情。

《React即时通信UI实战》课程,利用 Storybook、Styled-components、React-Spring 打造属于自己的组件库。

新书《JavaScript 基础语法详解》已上架,可在京东、当当、淘宝等各大电商购买