六、计算属性(1)

本章概要

  • 计算属性的定义
  • 计算属性的缓存
  • v-for和v-if替代方案

在模板中使用表达式非常方便,但如果表达式的逻辑过于复杂,模板就会变得臃肿不堪且难以维护。例如:

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

Mustache 语法中的表达式调用了 3 个方法来最终实现字符串的反转,逻辑过于复杂,如果在模板中还要多次引用此处的翻转字符串,就更加难以处理了,这时就应该使用计算属性。

6.1 计算属性的定义

计算属性是以函数形式,在选项对象的 computed 选项中定义。将上述字符串翻转的功能使用计算属性实现。如下:
例6-1

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>计算属性</title>
	</head>
	<body>
		<div id="app">
		  <p>原始字符串: {{ message }}</p>
		  <p>计算后的反转字符串: {{ reversedMessage }}</p>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
        <script>
        	const vm = Vue.createApp({
        	  data() {
        	    return {
        	        message: '独行月球'
        	    }
        	  },
        	  computed: {
        	  	// 计算属性的 getter
        	    reversedMessage(){
        	      return this.message.split('').reverse().join('');
        	    }
        	  }
        	}).mount('#app');
        </script>
	</body>
</html>

声明了一个计算属性 reversedMessage ,可以像普通属性一样将数据绑定到模板中的计算属性。
渲染结果如下:
![image.png](https://img-blog.csdnimg.cn/img_convert/3bd9a7fb66bbcd1d53f602f9d7f13e54.png#clientId=uece0b4ac-8378-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=114&id=u4de9326f&margin=[object Object]&name=image.png&originHeight=114&originWidth=435&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5393&status=done&style=none&taskId=ue7cb9db9-09a3-4e82-b4b6-f488012eb60&title=&width=435)
当 message 属性的值改变时,reversedMessage 的值也会自动更新,并且会自动同步更新 DOM 部分。在浏览器的 Console 窗口中修改 vm.message 的值,可以发现 reversedMessage 的值也会随之改变。
计算属性默认只有 getter,因此是不能直接修改计算属性的,如果需要,则可以提供一个 setter 。如下:
例6-2

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>计算属性的getter和setter</title>
	</head>
	<body>
		<div id="app">
		  <p>First name: <input type="text" v-model="firstName"></p>
		  <p>Last name: <input type="text" v-model="lastName"></p>
		  <p>{{ fullName }}</p>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
        <script>
        	const vm = Vue.createApp({
                data() {
                    return {
                        firstName: 'Smith',
                        lastName: "Will"    
                    }
                },
                computed: {
                  	fullName: {
                	    // getter
                	    get() {
                	        return this.firstName + ' ' + this.lastName
                	    },
                	    // setter
                	    set(newValue) {
                	        let names = newValue.split(' ')
                	        this.firstName = names[0]
                	        this.lastName = names[names.length - 1]
                	    }
                	}
                }
            }).mount('#app');
        </script>
	</body>
</html>

任意修改 firstName 或 lastName 的值,fullName 的值也会自动更新,这是调用它的 getter() 函数实现的。在浏览器的 Console 窗口输入 vm.fullName = “zhang san”,可以看到 firstName 和 lastName 的值也同时发生了改变,这是调用 fullName 的 setter 函数实现的。

6.2 计算属性的缓存

复杂的表达式也可以放到方法中实现,然后在绑定表达式中调用方法即可。以下代码使用方法实现了字符串的翻转,最终结果和 例6-1 使用计算属性相同。如下:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>计算属性</title>
	</head>
	<body>
		<div id="app">
		  <p>原始字符串: {{ message }}</p>
		  <p>方法调用后的反转字符串: {{ reversedMessage() }}</p>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
            const vm = Vue.createApp({
                data() {
                    return {
                        message: '我爱吃番茄'    
                    }
                },
                methods: {
                    reversedMessage() {
                        return this.message.split('').reverse().join('');
                    }
                }
            }).mount('#app');
		</script>
	</body>
</html>

计算属性是基于它的响应式依赖进行缓存的,只有在计算属性的相关响应式依赖发生改变时才会更新值。
这就意味着只要message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不会再次执行函数;而如果采用方法,那么不管什么时候访问 reversedMessage() ,该方法都会被调用。
为了对计算属性和方法的区别有更直观的认识。以下代码同事使用方法和计算属性,如下:
例6-4

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>计算属性</title>
	</head>
	<body>
		<div id="app">
		  <p>原始字符串: {{ message }}</p>
		  <p>计算后的反转字符串: {{ reversedMessage }}</p>
		  <p>方法调用后的反转字符串: {{ reversedMessage2() }}</p>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
                data() {
                    return {
                        message: '我爱吃西红柿'        
                    }
                },
                computed: {
                    // 计算属性的 getter
                    reversedMessage: function () {
                        alert("计算属性");
                        return this.message.split('').reverse().join('');
                    }
                },
                methods: {
                    reversedMessage2: function () {
                    	alert("方法");
                        return this.message.split('').reverse().join('');
                    }
                }
            }).mount('#app');
            let msg = vm.reversedMessage;
            msg = vm.reversedMessage2();
		</script>
	</body>
</html>

在计算属性 reversedMessage 的 getter() 函数和 reversedMessage2() 方法中调用 alert() 语句显示一个消息框,在根组件实例构建后,分别访问 vm.reversedMessage 计算属性和调用 vm.reversedMessage2() 方法。
使用浏览器打开该页面,可以依次看到“计算属性”“方法”“方法”共三个消息框,前两个消息框是模板中的 Mustache 标签被替换时显示的,最后一个“方法”消息框是代码最后调用vm.reversedMessage2() 方法显示的,可以看到最后对 vm.reversedMessage 计算属性的访问并没有弹出消息框,这是因为它所依赖的 message 属性并未发生改变。
下面代码中的计算属性 now 在初次渲染后不会再更新,因为 Date.now() 不是响应式依赖

computed: {
    now:function(){
        return Date.now();
    }
}

那么为什么需要缓存呢?
假设有一个性能开销比较大的计算属性 A ,它需要遍历一个巨大的数组并做大量的计算,然后可能还有其它的计算属性依赖于 A。如果没有缓存,将不可避免地多次执行 A 的 getter。
如果业务实现不希望有缓存,那么可以使用方法来代替。

6.3 v-for和v-if替代方案

使用计算属性替代 v-for 和 v-if 一起使用的情况。如下:
例6-5

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>v-for与计算属性</title>
	</head>
	<body>
		<div id="app">
		  <h1>已完成的工作计划</h1>
			<ul>
				<li v-for="plan in completedPlans">
					{{plan.content}}
				</li>
			</ul>
			<h1>未完成的工作计划</h1>
			<ul>
				<li v-for="plan in incompletePlans">
					{{plan.content}}
				</li>
			</ul>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
                data() {
                    return {
                        plans: [
                        	{content: '狗了个狗', isComplete: false},
                        	{content: '买菜', isComplete: true},
                        	{content: '写PPT', isComplete: false},
                        	{content: '做饭', isComplete: true},
                        	{content: '打羽毛球', isComplete: false}
                        ]
                    }
                },
                computed: {
                    completedPlans() {
                        return this.plans.filter(plan => plan.isComplete);
                    },
                    incompletePlans(){
                        return this.plans.filter(plan => !plan.isComplete);
                    }
                }
			}).mount('#app');
		</script>
	</body>
</html>

Vue.js 的作者不建议将 v-for和 v-if 一起使用,这是因为即使由于 v-if 指令的作用只渲染了部分元素,但在每次重新渲染的时候仍要遍历整个列表,而不论渲染的元素内容是否发生了改变。
采用计算属性过滤后再遍历,可以获得以下好处:

  • 过滤后的列表只会在 plans 数组发生相关变化时才被重新计算,过滤更高效
  • 使用 v-for=“plan in completedPlans” 之后,在渲染的时候只遍历已完成的计划,渲染更高效
  • 解耦渲染的逻辑,可维护性(对逻辑的更改和扩展)更强
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小熊猫呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值