Vue中的Methods和Computed的区别

290

methodcomputed在Vue.js中的主要区别是什么?

它们看起来对我来说是相同的并且可以互换使用。


或许对您有帮助:https://vuejs.org/v2/guide/computed.html#Computed-Properties - DunDev
2
@xDreamCoding,你提供的答案确实回答了这个问题,但这个问题并不是重复的。此外,它更有名。 - Romain Vincent
请参考文档,该文档在“计算属性 vs 方法”一节中对这个主题进行了一些解释:https://vuejs.org/v2/guide/computed.html - Kshitij Dhyani
12个回答

363

计算属性和方法在Vue中非常不同,在大多数情况下不能互换使用。

计算属性

计算值更适合称为计算属性。实际上,当Vue实例化时,计算属性会被转换为具有getter和setter的Vue属性。基本上,您可以将计算值视为派生值,每当用于计算它的底层值之一更新时,它将自动更新。您不需要调用计算属性,也不接受任何参数。您可以像引用数据属性一样引用计算属性。这是来自文档的经典示例:

computed: {
  // a computed getter
  reversedMessage: function () {
    // `this` points to the vm instance
    return this.message.split('').reverse().join('')
  }
}

这样在DOM中引用:

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

计算属性对于操作 Vue 实例中已有的数据非常有用。每当你需要过滤或转换数据时,通常会使用计算属性来实现该目的。
data:{
    names: ["Bob", "Billy", "Mary", "Jane"]
},
computed:{
    startsWithB(){
        return this.names.filter(n => n.startsWith("B"))
    }
}

<p v-for="name in startsWithB">{{name}}</p>

为了避免重复计算不需要重新计算的值(例如在循环中可能不需要重新计算),计算值也会被缓存。

方法

方法只是绑定到Vue实例的函数。只有在显式调用它时才会被评估。像所有JavaScript函数一样,它接受参数,并且每次调用时都会被重新评估。方法在任何函数有用的情况下都很有用。

data:{
    names: ["Bob", "Billy", "Mary", "Jane"]
},
computed:{
    startsWithB(){
        return this.startsWithChar("B")
    },
    startsWithM(){
        return this.startsWithChar("M")
    }
},
methods:{
    startsWithChar(whichChar){
        return this.names.filter(n => n.startsWith(whichChar))
    }
}

Vue的文档非常好,易于访问。我推荐它。


3
如果用户提供了两个输入,例如从摄氏度到华氏度的温度转换和反之亦然,其中两个输入可以确定彼此的值。请参见http://albireo.ch/temperatureconverter/ 并且这两个输入会自动响应而无需按转换按钮。那么使用computed还是methods更适合? - Bootstrap4
3
对于具有输入环形关系的特定 UI,我会选择使用方法(methods)。https://codepen.io/Kradek/pen/gROQeB?editors=1010 - Bert
3
这里有一个使用计算属性的例子,但它更加复杂。https://codepen.io/Kradek/pen/gROQeB?editors=1010 - Bert
4
一个方法...只有在你明确调用它时才会被评估。但是根据这个视频并非如此:https://www.youtube.com/watch?v=O14qJr5sKXo - Cameron Hudson
3
在视频示例中,这些方法被评估是因为它们在模板中被明确引用。这里有一个示例演示了差异。请注意,只有当数据更改并且如果它们在模板中被明确引用时,才会调用这些方法。 - Bert
显示剩余6条评论

86

由于@gleenk要求提供一个实际的例子来清晰地展示方法和计算属性之间的缓存和依赖差异,我将展示一个简单的场景:

app.js

new Vue({
    el: '#vue-app',
    data: {
        a: 0,
        b: 0,
        age: 20
    },
    methods: {
        addToAmethod: function(){
            console.log('addToAmethod');
            return this.a + this.age;
        },
        addToBmethod: function(){
            console.log('addToBmethod');
            return this.b + this.age;
        }
    },
    computed: {
        addToAcomputed: function(){
            console.log('addToAcomputed');
            return this.a + this.age;
        },
        addToBcomputed: function(){
            console.log('addToBcomputed');
            return this.b + this.age;
        }
    }
});

这里有两个方法和两个计算属性,它们完成相同的任务。方法addToAmethodaddToBmethod以及计算属性addToAcomputedaddToBcomputed都会将+20(即age值)添加到ab中。关于方法,无论一个特定方法的依赖项是否已更改,它们都会在任何列出的属性上执行每次操作时调用。对于计算属性,只有当依赖项发生更改时才会执行代码;例如,引用A或B的特定属性值之一将分别触发addToAcomputedaddToBcomputed
方法和计算描述看起来非常相似,但正如@Abdullah Khan已经指出的那样,它们并不是相同的东西!现在让我们尝试添加一些html来同时执行所有内容,并查看差异所在。

方法案例演示

new Vue({
    el: '#vue-app',
    data: {
        a: 0,
        b: 0,
        age: 20
    },
    methods: {
        addToAmethod: function(){
            console.log('addToAmethod');
            return this.a + this.age;
        },
        addToBmethod: function(){
            console.log('addToBmethod');
            return this.b + this.age;
        }
    }
});
<!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title>VueJS Methods - stackoverflow</title>
            <link href="style.css" rel="stylesheet" />
            <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.min.js"></script>
    
        </head>
        <body>
            <div id="vue-app">
                <h1>Methods</h1>
                <button v-on:click="a++">Add to A</button>
                <button v-on:click="b++">Add to B</button>
                <p>Age + A = {{ addToAmethod() }}</p>
                <p>Age + B = {{ addToBmethod() }}</p>
            </div>
        </body>
        
        <script src="app.js"></script>
    </html>

解释结果

当我点击按钮"添加到A"时,所有的方法都会被调用(如上方控制台日志屏幕结果所示),addToBmethod()也会被执行,但我没有按下"添加到B"按钮;与B相关的属性值没有改变。如果我们决定点击按钮"添加到B",同样的行为会发生,因为两个方法都会被独立调用,而不考虑依赖关系的变化。根据这种情况,这是一个不好的做法,因为我们每次都要执行这些方法,即使依赖关系没有改变。这真的很耗资源,因为不存在缓存未更改的属性值。

method button method

计算属性案例演示

new Vue({
    el: '#vue-app',
    data: {
        a: 0,
        b: 0,
        age: 20
    },

    computed: {
        addToAcomputed: function(){
            console.log('addToAcomputed');
            return this.a + this.age;
        },
        addToBcomputed: function(){
            console.log('addToBcomputed');
            return this.b + this.age;
        }
    }
});
<!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title>VueJS Computed properties - stackoverflow</title>
            <link href="style.css" rel="stylesheet" />
            <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.min.js"></script>
        </head>
        <body>
            <div id="vue-app">
                <h1>Computed Properties</h1>
                <button v-on:click="a++">Add to A</button>
                <button v-on:click="b++">Add to B</button>
                <p>Age + A = {{ addToAcomputed }}</p>
                <p>Age + B = {{ addToBcomputed }}</p>
            </div>
        </body>
        
        <script src="app.js"></script>
    </html>

解释的结果

当我点击按钮"添加到A"时,只有计算属性addToAcomputed被调用,因为如我们所说,只有在依赖项更改时才会执行计算属性。由于我没有按下"添加到B"按钮,且B的年龄属性值未更改,因此没有理由调用和执行计算属性addToBcomputed。因此,在某种意义上,计算属性像一种缓存一样维护着B属性的"相同未更改"的值。在这种情况下,这被认为是良好的实践

computed button computed


7
当按下一个按钮时,为什么所有的方法都会被执行?这是什么原因/逻辑? - Abdul Rehman
3
@Bsienn 这是一个好问题:基本上,Vue 不知道根据更新了什么来运行哪个方法。这就是计算属性所做的操作,它们监视需要计算或重新计算的变量,并且只在需要时运行。 - Giulio Bambini
3
使用方法的原因是什么?看起来计算属性更好(假设我们正在谈论“get”方法)... - user3529607
7
@user3529607,但计算属性不接收参数。 - Rodion Golovushkin
3
据我所理解,方法可以在挂载或创建Vue实例时很有用。而计算属性则不能做到这一点。此外,我们必须返回计算属性的值。 - Dhaval Chheda
1
好的回答。谢谢@Giulio Bambini - Faridul Khan

29

以下是本问题的详细解析:

何时使用方法

  • 响应DOM中发生的某些事件。
  • 当组件中发生某些事情时调用函数。
  • 可以从计算属性或观察器中调用方法。

何时使用计算属性

  • 需要从现有数据源组合新数据时。
  • 您在模板中使用一个变量,该变量是由一个或多个数据属性构建而成的。
  • 您想将复杂嵌套的属性名称简化为更易读和使用的名称(但在原始属性更改时更新它)。
  • 需要引用模板中的值。 在这种情况下,创建计算属性是最好的选择,因为它被缓存。
  • 需要监听多个数据属性的更改。

1
清晰而且最好的答案。谢谢Diego - user8737957
1
非常清晰,正是我所需要的。大多数答案都解释了为什么计算值很好,但我已经知道了这一点。实际上,我想知道的是,如果计算值如此出色,为什么你还要使用方法。这至少解释了其中的一部分。 - redfox05

24

来自文档

计算属性是根据它们的依赖项进行缓存的。只有在其某些依赖项已更改时,计算属性才会重新评估。

如果您想要缓存数据,请使用计算属性;另一方面,如果您不想要缓存数据,请使用简单方法属性。


1
你好,你能否写一个有用的例子来展示实际使用的差异? - Davide De Maestri
@gleenk 我会添加一个实际的例子来展示方法和计算属性之间的缓存/依赖差异。希望你会喜欢它。 - Giulio Bambini
谢谢@GiulioBambini - Davide De Maestri

20

计算属性与方法的区别之一是:假设我们有一个函数会返回计数器的值(计数器只是一个变量)。让我们看看在计算属性方法中函数的行为。

计算属性

在第一次执行函数内部的代码时,Vue.js将在缓存中存储计数器的值(以便更快地访问)。但是,当我们再次调用该函数时,Vue.js不会再次执行该函数内部编写的代码。它首先检查计数器是否有任何更改。如果有任何更改,它才会重新执行函数内部的代码。如果计数器没有发生更改,Vue.js将不会重新执行该函数,并且仅简单地从缓存中返回先前的结果。

方法

这就像JavaScript中的普通方法。每当我们调用该方法时,它都会始终执行函数内部的代码,而不管计数器是否发生了更改。

方法总是无论代码是否更改都会重新执行代码。而计算属性只有在其依赖项的值更改时才会重新执行代码。否则,它将从缓存中返回上一个结果而不重新执行代码。


6

计算属性

计算属性也被称为计算值。这意味着它们可以随时更新和更改。同时,它会缓存数据直到数据发生变化。当Vue被实例化时,计算属性被转换为一个属性。

还有一件事要分享,您不能在计算属性中传递任何参数,因此在调用任何计算属性时不需要使用括号。

方法

方法与函数相同并且工作方式也相同。此外,除非您调用它,否则该方法不执行任何操作。而且,像所有JavaScript函数一样,它接受参数,并且每次调用时都会重新计算。之后,它们无法缓存值。

在调用方法时需要使用括号,并且您可以发送一个或多个参数。


那么你的意思是计算值在初始化时计算,而方法只有在调用时才计算? - redfox05

4

我也遇到了同样的问题。对我来说,更清晰的表述是:

  1. 当Vue.js看到v-on指令后面跟着一个方法时,它就知道要调用哪个方法以及何时调用它。
<button v-on:click="clearMessage">Clear message</button> // @click
// method clearMessage is only called on a click on this button

<input v-model="message" @keyup.esc="clearMessage" @keyup.enter="alertMessage" />
/* The method clearMessage is only called on pressing the escape key
and the alertMessage method on pressing the enter key */

当一个方法没有使用v-on指令被调用时,它将在更新DOM(或者需要重新渲染页面的部分)的页面上每次事件被触发时被调用。即使该方法与触发事件无关。
<p>Uppercase message: {{ messageUppercase() }}</p>
methods: {
   messageUppercase() {
      console.log("messageUpercase");
      return this.message.toUpperCase();
   }
}
/* The method `messageUppercase()` is called on every button click, mouse hover 
or other event that is defined on the page with the `v-on directive`. So every
time the page re-renders.*/
  1. 计算属性仅在被引用的属性值发生改变时才会被调用,并且计算属性的函数定义中必须使用this关键字来引用这个属性。
<p>Uppercase message: {{ messageUppercase }}</p> 
data() {
 return {
    message: "I love Vue.js"
   }
 },
computed: {
 messageUppercase() {
    console.log("messageUpercase");
    return this.message.toUpperCase();
 }
}
/* The computed property messageUppercase is only called when the propery message is
changed. Not on other events (clicks, mouse hovers,..) unless of course a specific 
event changes the value of message.  */

这里需要注意的是,如果一个方法没有被v-on指令调用,那么最好使用computed属性作为最佳实践。

2
在Vue 3中提供了Composition API,也可以作为Vue 2的插件使用。其中的方法和计算属性使用不同的语法:
举例来说,computed是一个函数,默认接受getter回调函数作为参数,并返回一个基于其他属性(如ref、reactive或store状态)的不可变ref。
import {computed,ref} from 'vue'

export default{

setup(){
  const count=ref(0);
  
  const doubleCount=computed(()=>count.value*2) 

 return {count,doubleCount} //expose the properties to the template 
 }
}

方法

是纯 JavaScript 函数,在 Vue 和原生 js 中的行为相同,可以在模板中使用并作为事件处理程序。不应将其用于渲染目的,否则可能会导致无限渲染等问题。

import {computed,ref} from 'vue'

export default{

setup(){
  const count=ref(0);
  
  const doubleCount=computed(()=>count.value*2) 
 
  function increment(){
   ref.value++
 }

 return {count,doubleCount,increment} //expose the properties/functions to the template 
 }
}

区别:

计算属性:

  • 它被视为不可变属性而不是函数进行评估
  • 它观察另一个属性并根据该属性返回一个属性。
  • 它不能带参数。
  • 可以使用 watch 属性进行监视

方法:

  • 用于重构计算属性/观察者属性或其他函数内部的代码
  • 用作事件处理程序
  • 不应在模板中调用它以避免渲染问题。

2

我会尝试补充其他成员的答案。这个例子和解释使我完全理解了计算属性的要点。我希望在阅读我的帖子后,你也能意识到它。


如果你需要改变数据,你必须使用方法。当你需要改变现有数据的呈现方式时,你将使用计算属性。随着你练习这两个概念,你会轻松地使用它们。以下是一些关键点:

  1. 计算属性必须始终返回一个值;
  2. 计算属性仅用于转换数据,而不是为我们的表示层更改数据 | 它们不应更改现有数据。

正如你已经阅读或运行了我的示例代码后,你会发现只有在计算属性中呈现的值被改变(无论是在方法内部、用户输入还是其他方式),计算属性将被重新计算并缓存。 但每次调用方法时,它都将被执行,而不考虑结果(例如,在我的示例中,当一个值达到 0 值时,计算属性不再被重新计算)

在这个例子中,有一个简单的系统;其中你有:

  • 自己的现金;
  • 你在银行账户中的现金;
  • 从你的银行账户中取款的可能性;
  • 从某个人那里借一些钱(有无限的钱)。

new Vue({
  el: '#app',
  data: {
    infinity: Infinity,
    value: 3,
    debt: -6,
    cash: 9,
    moneyInBank: 15,
  },

  computed: {
    computedPropRemainingCashFundsIfPaid: function() {
      console.log('computedPropRemainingCashFundsIfPaid');
      return this.debt + this.cash;
    },
    computedPropRemainingTotalFunds: function() {
      console.log('computedPropRemainingTotalFunds');
      return this.cash + this.moneyInBank + this.debt;
    }
  },
  methods: {
    depositFunds: function(from, to, value, limit = false) {
      if (limit && (this[to] + value) >= 0) { // if you try to return greater value than you owe
        this[from] += this[to];
        this[to] = 0;
      } else if (this[from] > value && this[from] - value >= 0) { // usual deposit
        this[to] += value;
        this[from] -= value;
      } else { // attempt to depost more than you have
        this[to] += this[from];
        this[from] = 0;
      }
    },
    repayADebt: function() {
      this.value = Math.abs(this.value);
      if (this.debt < 0) {
        this.depositFunds('cash', 'debt', this.value, true);
      }
      console.log('Attempt to repayADebt', this.value);
    },
    lendAmount: function() {
      this.depositFunds('infinity', 'debt', -Math.abs(this.value));
      console.log('Attempt to lendAmount', this.value);
    },
    withdraw: function() {
      if (this.moneyInBank) {
        this.depositFunds('moneyInBank', 'cash', this.value);
      }
      console.log('Attempt to withdraw', this.value);
    }
  }
});
* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
  overflow-wrap: break-word;
}

html {
  font-family: "Segoe UI", Tahoma, Geneva, Verdana;
  font-size: 62.5%;
}

body {
  margin: 0;
  font-size: 1.6rem;
}

#app {
  margin: 3rem auto;
  max-width: 50vw;
  padding: 1rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
}

label,
input {
  margin-bottom: 0.5rem;
  display: block;
  width: 100%;
}

label {
  font-weight: bold;
}

ul {
  list-style: none;
  margin: 1rem 0;
  padding: 0;
}

li {
  margin: 1rem 0;
  padding: 1rem;
  border: 1px solid #ccc;
}

.grid {
  display: grid;
  grid: 1fr / 1fr min-content 1fr min-content;
  gap: 1rem;
  align-items: center;
  margin-bottom: 1rem;
}

.grid> :is(button, input) {
  height: 3rem;
  margin: 0;
}

.computed-property-desc {
  padding: 1rem;
  background-color: rgba(0, 0, 0, 0.3);
  text-align: justify;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>A First App</title>
  <link rel="stylesheet" href="styles.css" />
</head>

<body>
  <div id="app">

    <h1>Computed Properties Guide</h1>
    <p style="background-color: bisque;">
      Let's assume that you have <span v-once>{{ cash }}</span>$; And you need to pay a debt=<span v-once>{{ debt }}</span>
    </p>
    <p>Your bank account: {{ moneyInBank }}$ <button v-on:click="withdraw(value)">Withdrow {{ value }}$ from
                bank</button></p>
    <p>Your cash: {{ cash }}$</p>
    <p>Your debt: {{ debt }}$ <button v-on:click="lendAmount(value)">Lend {{ value }}$ from Infinity</button></p>
    <div class="grid">
      <button v-on:click="repayADebt(value)">Repay a debt</button>
      <span>in amout of</span>
      <input type="text" v-model.number="value">
      <span>$</span>
    </div>

    <p>computedPropRemainingCashFundsIfPaid/<br><mark>Available funds in case of debt repayment</mark> = {{ computedPropRemainingCashFundsIfPaid }}$</p>
    <p>computedPropRemainingTotalFunds = {{ computedPropRemainingTotalFunds }}$</p>

    <p class="computed-property-desc">when you need to change data, you will use methods. And When you need to change the presentation of existing data, you will use computed properties. As you practice both concepts, it will become easier which one should you use. Very important notes:
      1. it must always return a value; 2. computed properties are only used for transforming data and not for chaning it for our presentation layer | they should not alter or change the existing data</p>

  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
</body>

</html>


1
这里是Vue3文档的内容 - 请查看以下示例:

就最终结果而言,这两种方法确实完全相同。但是,区别在于计算属性基于它们的响应依赖项进行缓存。只有在其某些响应依赖项发生更改时,计算属性才会重新评估。[...] 相比之下,方法调用将始终在重新渲染时运行函数。

附加链接
  1. 方法
  2. 计算属性

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接