概述
在Vue开发中,模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。当你想要在模板中多次引用相同表达式时,就会更加难以处理。所以,对于任何复杂逻辑,你都应当使用计算属性。本文主要讲解Vue中的计算属性和侦听器,仅供学习分享使用,如有不足之前,还请指正。
计算属性
计算属性步骤:
1. 在computed属性中增加reverseMsg方法,如下所示:
1 <script type="text/javascript"> 2 var vm = new Vue({ 3 el: '#app', 4 data: { 5 msg: 'welcome to vue world!!!' 6 7 }, 8 computed: { 9 reverseMsg: function() { 10 // `this` 指向 vm 实例 11 return this.msg.split('').reverse().join(''); 12 } 13 } 14 15 }); 16 </script>
2. 在Html中进行引用,你可以像绑定普通属性一样在模板中绑定计算属性。
Vue 知道 vm.reverseMsg 依赖于 vm.msg,因此当 vm.msg 发生改变时,所有依赖 vm.reverseMsg 的绑定也会更新。如下所示:
1 <p>原始信息: {{ msg }}</p> 2 <p>计算属性反转信息: {{ reverseMsg }}</p>
采用表达式的方式 ,则是如下所示:
1 <span>{{ msg.split('').reverse().join('') }}</span>
在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 msg 的翻转字符串。此处对比一下,采用计算属性的方式,则更加简洁明了。
计算属性缓存 vs 方法
你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果,我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。
如下所示,声明一个方法:
1 <script type="text/javascript"> 2 var vm = new Vue({ 3 el: '#app', 4 data: { 5 msg: 'welcome to vue world!!!' 6 }, 7 methods: { 8 reversedMsg: function() { 9 return this.msg.split('').reverse().join(''); 10 } 11 } 12 13 }); 14 </script>
在Html中进行引用,如下所示:
1 <p>Reversed message: "{{ reversedMsg() }}"</p>
差异:不同的是计算属性是基于它们的响应式依赖进行缓存的。 只在相关响应式依赖发生改变时它们才会重新求值。 这就意味着只要 msg 还没有发生改变,多次访问 reversedMsg计算属性会立即返回之前的计算结果,而不必再次执行函数。 即:计算属性只有当依赖属性发生改变时,才重新计算,而函数需要每次都重新计算。
也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖: 相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
1 <script type="text/javascript"> 2 var vm = new Vue({ 3 el: '#app', 4 data: { 5 msg: 'welcome to vue world!!!' 6 7 }, 8 computed: { 9 now: function() { 10 return Date.now().toString(); 11 } 12 13 }); 14 </script>
如下所示,将不会随着时间的改变而触发。
1 <p>{{now}}</p>
如果你不希望有缓存,请用方法来替代。
计算属性 vs 侦听属性
如下所示:有两个data属性,firstName和lastName,fullName依赖说前两个变化而变化。如果需要采用侦听属性,需要对firstName和lastName进行侦听。
1 <script type="text/javascript"> 2 var vm = new Vue({ 3 el: '#app', 4 data: { 5 firstName: 'Foo', 6 lastName: 'Bar', 7 fullName: 'Foo Bar', 8 }, 10 computed: { 11 fullName2: function() { 12 return this.firstName + ' ' + this.lastName; 13 } 15 }, 16 watch: { 17 firstName: function(val) { 18 this.fullName = val + ' ' + this.lastName; 19 }, 20 lastName: function(val) { 21 this.fullName = this.firstName + ' ' + val; 22 } 24 } 26 }); 27 </script>
上面代码是命令式且重复的。将它与计算属性的版本进行比较。
1 <div id="demo">{{ fullName }}</div> 2 <div id="demo">{{ fullName2 }}</div>
如上所示:fullName采用侦听属性,fullName2采用计算属性,对比一下,好得多了,不是吗?
计算属性的 setter
计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter :如下所示:
1 fullName3: { 2 //getter 3 get: function() { 4 return this.firstName + ' ' + this.lastName; 5 }, 6 //setter 7 set: function(newValue) { 8 console.log('newValue=' + newValue); 9 var names = newValue.split(' '); 10 this.firstName = names[0]; 11 this.lastName = names[names.length - 1]; 12 } 13 }
在UI中引用的方式是一样的。当fullName3的值发生改变时,将更新firstName和lastName。
1 <p>{{fullName3}}</p>
侦听器
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。如下所示:
1 <script type="text/javascript"> 2 var vm = new Vue({ 3 el: '#app', 4 data: { 5 question: '', 6 answer: 'I cannot give you an answer until you ask a question!' 7 8 }, 9 watch: { 10 // 如果 `question` 发生改变,这个函数就会运行 11 question: function(newQuestion, oldQuestion) { 12 if (newQuestion == '') { 13 this.answer = 'Waiting for you to stop typing...'; 14 } else { 15 this.answer = '请回答'; 16 } 17 } 18 } 19 }); 20 </script>
在页面中绑定属性
1 <p>Ask a yes/no question: 2 <input v-model="question"> 3 </p> 4 <p>{{ answer }}</p>
在这个示例中,使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
在本示例中用的全部代码 ,如下所示:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>一起学Vue之计算属性和侦听器</title> 6 <!-- 开发环境版本,包含了有帮助的命令行警告 --> 7 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 8 </head> 9 <body> 10 <div id="app"> 11 <span>{{msg}}</span> 12 <h2>计算属性</h2> 13 <!-- 14 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如: 15 --> 16 <br /> 17 <span> 18 {{ msg.split('').reverse().join('') }} 19 </span> 20 <!-- 21 在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 msg 的翻转字符串。 22 当你想要在模板中多次引用此处的翻转字符串时,就会更加难以处理。 23 所以,对于任何复杂逻辑,你都应当使用计算属性 24 --> 25 <p>原始信息: {{ msg }}</p> 26 <p>计算属性反转信息: {{ reverseMsg }}</p> 27 <!-- 28 这里我们声明了一个计算属性 reverseMsg。我们提供的函数将用作属性 vm.reverseMsg 的 getter 函数: 29 --> 30 <!-- 31 你可以像绑定普通属性一样在模板中绑定计算属性。Vue 知道 vm.reverseMsg 依赖于 vm.msg, 32 因此当 vm.msg 发生改变时,所有依赖 vm.reverseMsg 的绑定也会更新。 33 --> 34 <h2>计算属性缓存 vs 方法</h2> 35 <p>Reversed message: "{{ reversedMsg() }}"</p> 36 <!-- 37 你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果: 38 --> 39 <!-- 40 我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。 41 然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。 42 只在相关响应式依赖发生改变时它们才会重新求值。 43 这就意味着只要 msg 还没有发生改变,多次访问 reversedMsg 计算属性会立即返回之前的计算结果,而不必再次执行函数。 44 即:计算属性只有当依赖属性发生改变时,才重新计算,而函数需要每次都重新计算。 45 --> 46 <!-- 47 也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖: 48 相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。 49 如果你不希望有缓存,请用方法来替代 50 --> 51 <p>{{now}}</p> 52 <h2>计算属性 vs 侦听属性</h2> 53 <div id="demo">{{ fullName }}</div> 54 <!-- 55 上面代码是命令式且重复的。将它与计算属性的版本进行比较: 56 --> 57 <div id="demo">{{ fullName2 }}</div> 58 <!-- 59 好得多了,不是吗? 60 --> 61 <h2>计算属性的 setter</h2> 62 <!-- 63 计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter : 64 --> 65 <p>{{fullName3}}</p> 66 <h2>侦听器</h2> 67 <!-- 68 虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。 69 这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。 70 当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。 71 --> 72 <p>Ask a yes/no question: 73 <input v-model="question"> 74 </p> 75 <p>{{ answer }}</p> 76 <!-- 77 在这个示例中,使用 watch 选项允许我们执行异步操作 (访问一个 API), 78 限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。 79 这些都是计算属性无法做到的。 80 --> 81 </div> 82 <script type="text/javascript"> 83 var vm = new Vue({ 84 el: '#app', 85 data: { 86 msg: 'welcome to vue world!!!', 87 firstName: 'Foo', 88 lastName: 'Bar', 89 fullName: 'Foo Bar', 90 question: '', 91 answer: 'I cannot give you an answer until you ask a question!' 92 93 94 }, 95 methods: { 96 reversedMsg: function() { 97 return this.msg.split('').reverse().join('') 98 } 99 }, 100 computed: { 101 reverseMsg: function() { 102 // `this` 指向 vm 实例 103 return this.msg.split('').reverse().join(''); 104 }, 105 now: function() { 106 return Date.now().toString(); 107 }, 108 fullName2: function() { 109 return this.firstName + ' ' + this.lastName; 110 }, 111 fullName3: { 112 //getter 113 get: function() { 114 return this.firstName + ' ' + this.lastName; 115 }, 116 //setter 117 set: function(newValue) { 118 console.log('newValue=' + newValue); 119 var names = newValue.split(' '); 120 this.firstName = names[0]; 121 this.lastName = names[names.length - 1]; 122 } 123 } 124 }, 125 watch: { 126 firstName: function(val) { 127 this.fullName = val + ' ' + this.lastName; 128 }, 129 lastName: function(val) { 130 this.fullName = this.firstName + ' ' + val; 131 }, 132 // 如果 `question` 发生改变,这个函数就会运行 133 question: function(newQuestion, oldQuestion) { 134 if (newQuestion == '') { 135 this.answer = 'Waiting for you to stop typing...'; 136 } else { 137 this.answer = '请回答'; 138 } 139 } 140 141 } 142 143 }); 144 </script> 145 </body> 146 </html>
备注
暴虎冯河,死而无悔者,吾不与也;必也,临事而惧,好谋而成者也。