方法与 Vue 中的计算

Methods vs Computed in Vue

methods 和 Vue.js 中的 computed 值之间的主要区别是什么?

它们看起来一样,可以互换。

计算值和方法在 Vue 中非常不同,在大多数情况下绝对不能互换。

计算属性

计算值更合适的名称是 computed property. In fact, when the Vue is instantiated, computed properties are converted into a property of the Vue with a getter and sometimes a setter. Basically you can think of a computed value as a derived value that will be automatically updated whenever one of the underlying values used to calculate it is updated. You don't call a computed and it doesn't accept any parameters. You reference a computed property just like you would a data property. Here's the classic example from the documentation:

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 的 documentation 非常好,而且易于访问。我推荐。

来自docs

..computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed.

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

由于@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;
        }
    }
});

这里我们有 2 个方法和 2 个计算属性来执行相同的任务。方法 addToAmethod & addToBmethod 和计算属性 addToAcomputed & addToBcomputed 都将 +20(即 age 值)添加到 ab。关于这些方法,它们是 both 调用 every 一次对列出的 any 执行操作属性,即使一个特定方法的依赖关系没有改变。对于计算属性,代码仅在依赖项发生更改时执行;例如,引用 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 - Whosebug</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”,则会出现相同的行为,因为这两种方法将再次独立于依赖项更改而被调用。根据这种情况,这是 不好的做法 因为我们每次都在执行这些方法,即使依赖关系没有改变。这真的很耗费资源,因为没有 属性 未更改值的缓存。

计算属性案例演示

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 - Whosebug</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 属性 维护“相同不变”的值。在这种情况下,这是考虑好的做法

计算属性

计算属性也称为计算值。这意味着,它们会更新并且可以随时更改。此外,它缓存数据直到它发生变化。实例化 Vue 时,计算属性将转换为 属性.

我还想分享一件事,您不能在计算属性中传递任何参数,这就是为什么在调用任何计算机时 属性 不需要括号。

方法

方法与函数相同,工作原理相同。此外,除非您调用方法,否则它什么也不做。此外,与所有 javascript 函数一样,它接受参数并在每次调用时重新计算。在那之后,他们不能缓存值

在方法调用中有括号,您可以在其中发送一个或多个参数。

computed和method的区别之一。假设我们有一个函数,它将 return 计数器值。(计数器只是变量)。让我们看看函数在 computedmethod

中的行为

计算

第一次执行时,函数内的代码将被执行,vuejs 会将计数器值存储在缓存中(以便更快地访问)。但是当我们再次调用该函数时,vuejs 不会再次执行该函数内部编写的代码。它首先检查是否对计数器进行了任何更改。如果进行了任何更改,那么它只会重新执行该函数内部的代码。如果没有对计数器进行任何更改,vuejs 将不会再次执行该函数。它只会 return 缓存中的先前结果。

方法

这就像 javascript 中的普通方法一样。每当我们调用该方法时,它总是会执行函数内部的代码,而不管对计数器所做的更改。

方法将始终重新执行代码,而不管代码中的更改。 where as computed 将重新执行代码,然后仅当其中一个依赖项的值发生更改时。否则它会在不重新执行

的情况下为我们提供缓存中的先前结果

这是这个问题的细目。

何时使用方法

  • 对 DOM
  • 中发生的某些事件做出反应
  • 当您的组件发生某些事情时调用一个函数。
  • 您可以从计算属性或观察者调用方法。

何时使用计算属性

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

偶然发现了同样的问题。对我来说,这样更清楚:

  1. 当Vue.js看到v-on directive后跟一个方法时,它确切地知道调用哪个方法 何时调用它。
<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 */
  1. 当一个方法被调用时没有v-on directive它将被调用每次页面上的事件被触发更新 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 word 在其函数定义中。
<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.  */

这里要注意的是,最好使用 computed 属性,以防未使用 v-on directive.

调用方法。

按照 vueJs 文档的简单方法:

相比之下,只要发生重新渲染,方法调用总是运行函数。

虽然计算 属性 只会在其某些反应性依赖项发生更改时才重新评估

在 Vue 3 附带的 vue 组合 API 中,它可作为 vue 2 的插件使用,方法和计算属性是不同的语法:

示例:

计算:

这是一个函数,默认采用 getter 回调作为参数,returns 一个基于其他 属性 的不可变引用,如引用、反应或存储状态。

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 和 vanilla 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 
 }
}

区别:

计算:

  • 它被计算为不可变的属性而不是函数
  • 它观察到​​另一个 属性 和 returns 一个基于那个的 属性。
  • 不能带参数。
  • 可以用手表看属性

方法:

  • 用于在computed/watcher属性或其他函数
  • 中重构代码
  • 用作事件处理程序
  • 不应在模板内调用它以避免呈现问题。

我会尽量补充其他会员的答案。这个例子和解释让我完全理解了计算属性的要点。我希望在阅读我的 post 之后,您也会意识到这一点。


如果你需要改变数据,你必须使用方法。当您需要更改现有数据的表示时,您将使用计算属性。当您练习这两个概念时,您将轻松使用它们。这里有一些关键点:

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

正如您已经阅读过它或在您 运行 我的示例代码之后,您将看到只有计算属性中显示的值被 bieng 更改(在方法内部或通过用户输入或通过其他方式),计算的 属性 将被重新计算并缓存。 但是每次调用一个方法,不管结果如何,都会执行(比如在我的例子中,当一个值达到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>

下面是 Vue3 documentation 所说的 - 查看示例:

For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed. [...] In comparison, a method invocation will always run the function whenever a re-render happens.

其他链接

  1. Methods
  2. Computed Properties

computed和methods的区别在于,在computed中定义一个函数,只有当答案改变时,它才从头开始执行函数,而methods每次调用时,都会从头开始执行函数。