目前工作中,很多公司都把vue作为自己的前端框架,vue的开发者和研究者也越来越多;不知道有多少人在研究使用vue的时候,对于computed和methods到底有什么区别处于模棱两可的状态,因为我们发现,想要实现一个需求,我们使用两种方式中的任何一个,基本上都可以实现,那么我们平时应该用什么更好呢?
首先我们先来看一下需求:
我们做一个购物车计算价格的功能,当用户自己修改数量的时候,后面的合计价格会跟着自动的改变,样式大家自己回去实现即可,我们主要看一下HTML结构和VUE的实现部分
<div id="app"> <ul class="box"> <li class="clearfix"> <div class="product"> <img v-bind:src="productData.shopImg" alt=""> <div> <span>{{productData['title']}}</span> <span>单价:¥{{productData['price']}}</span> </div> </div> <div class="number"> <input type="text" v-model="count"> <span>合计价格:</span> <span>¥{{met_sum()}}</span><!--使用methods中的方法实现--> <span>¥{{com_sum}}</span><!--使用computed中的计算属性--> </div> </li> </ul> </div> <script src='node_modules/vue/dist/vue.min.js'></script> <script> let vm = new Vue({ el: '#app', data: { //=>记录产品的基本信息 productData: { title: "Vue从入门到实战", price: 800, shopImg: 'img/vue.png' }, //=>记录产品的购买数量 count: 1 }, methods: { met_sum(){ return (this.count * this.productData.price) || 0; } }, computed: { com_sum(){ return (this.count * this.productData.price) || 0; } } }); </script>
从上面的代码中,我们发现,不管是定义在methods中的met_sum,还是定义在computed中的com_sum,实现的代码一样,最后呈现出的效果也是一样的,但是不管是从官方文档还是其他文献中,我们都了解到,当前这种需求,我们最好使用computed计算属性来完成,既然代码都一样,那么使用computed到底好在哪?
vue官方文档给我们的答案
https://cn.vuejs.org/v2/guide/computed.html
计算属性是基于它们的依赖进行缓存的
,计算属性只有在它的相关依赖发生改变时才会重新求值;而methods中定义的方法,每当触发重新渲染时,方法总会再次执行;所以,如果我们需要遍历一个特别大的数组进行大量计算的时候,使用methods中定义的方法,开销的内存较大,性能不好,而计算属性可以有效的避免这个问题;
不知道你们读完这段话后,理解了多少,反正我是一脸懵逼(可能我太笨吧),为了彻底搞明白这个,我自己翻阅了很多的文档(大部分都是抄袭官网文档的),做了很多测试,终于把他搞懂了,接下来,我分享一下自己对他的理解。
自己对于原生JS掌握的还是不错的,既然当我们 new Vue({...})
的时候,传递的配置项,大部分都会挂载到它的实例上,那么我把实例输出一下,看看能不能找到一些灵感。
不看不知道,一看吓一跳,我发现,写在methods中的met_sum是一个方法
,但是写在computed中的com_sum变为了一个属性
,而且这个属性是实现了get和set的(也就具备了双向数据交互的特点)
;
这样也就印证了,为啥在HTML结构中{{met_sum()}}
需要加括号执行,而{{com_sum}}
执行的时候不需要加括号(一但加括号了,就会报com_sum不是一个函数的错误)
研究到这,我更加懵逼了(看来会原生JS,有时候会让自己死的更惨啊 o(╥﹏╥)o),既然已经死透了,还有什么可怕的,看不明白,没啥,老子去看vue源码(没啥是看一遍源码解决不了的,如果有,那么再看一遍)(^_^)
VUE部分源码
var computedWatcherOptions = { lazy: true };function initComputed (vm, computed) { // $flow-disable-line var watchers = vm._computedWatchers = Object.create(null); // computed properties are just getters during SSR var isSSR = isServerRendering(); for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get; if ("development" !== 'production' && getter == null) { warn( ("Getter is missing for computed property \"" + key + "\"."), vm ); } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef); } else { if (key in vm.$data) { warn(("The computed property \"" + key + "\" is already defined in data."), vm); } else if (vm.$options.props && key in vm.$options.props) { warn(("The computed property \"" + key + "\" is already defined as a prop."), vm); } } } }function defineComputed ( target, key, userDef ) { var shouldCache = !isServerRendering(); if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef; sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop; sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; } if ("development" !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( ("Computed property \"" + key + "\" was assigned to but it has no setter."), this ); }; } Object.defineProperty(target, key, sharedPropertyDefinition); }function createComputedGetter (key) { return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value } } }
经过源码分析,我算是彻底理解了官网所谓的基于依赖进行缓存
是啥玩应了…(O(∩_∩)O哈哈~)
当我们在computed中设置一个方法后,例如:
let vm = new Vue({ ... data: { productData: { title: "Vue从入门到实战", price: 800, shopImg: 'img/vue.png' }, count: 1 }, computed: { com_sum(){ return (this.count * this.productData.price) || 0; } } });
1、vue会把com_sum作为一个属性,挂载到vue的实例vm上(并且做了Object.defineProperty的get和set设定,让其具备双向数据绑定),把原有com_sum方法执行,把计算并且返回的结果赋值给vm.com_sum属性
有些文档中说:computed中设置的方法名,不能和data中挂载的属性名重复,这个是完全不对的,相同也没关系,而且vue自己处理的结果就是建立一个和方法名相同的属性名(多亏自己研究一下,否则又被很多文档欺骗了)
建议大家computed中方法名不要和methods中的方法名相同,否则vue以methods中设定的方法为准,computed中的方法将无效(data、filter、methods、computed…中最好都不要有重复的名字)
metods中设定的方法,直接挂在到实例上,没有做其它而外的处理,就是挂在了一个方法而已
2、在开始执行computed中的com_sum方法,给vm.com_sum赋值的过程中,com_sum方法中用到了this.count和this.productData.price,那么此时vue会把com_sum和这两个属性建立依赖
3、当模板重新渲染加载的时候,vue内部会监听count以及productData.price有没有发生改变,如果没有发生改变,也就是计算属性的依赖没有改变(官网是这样描述的),那么结构中的{{com_sum}}使用的依然是第一次计算好的vm.com_sum,也就是相当于建立了计算属性的缓存,并不需要重新的执行com_sum方法;
4、当模板重新渲染加载的时候,如果count或者productData.price中有一个改变了,那么vue会把computed中的com_sum方法重新的执行,并且把最新计算返回的值赋值给vm.com_sum(相当于建立了缓存,下一次可以使用这次缓存的值),页面呈现出最新计算的数据
综合以上的研究,我们可以发现,如果我们编写的这个方法处理的数据量较大,为了避免每一次重新渲染页面都要把方法重新执行一遍(写在methods中的方法就是这样的机制),防止消耗的性能变多,我们最好把这样的方法写在计算属性computed中。
上面就是我目前在研究学习vue中遇到的问题,以及自己是如何一步步的去解决的,这里需要大家对原生JS有一定的了解,想要了解更多的有关VUE方面知识或者JS方面的知识,大家可以访问:http://www.html5train.com/这里有很多前端开发知识视频(讲解的很专业也很深入);大家也可以关注本微信公众号,我后期还会分享一些自己的学习笔记,和大家共同的学习进步。
此外,如果你感觉这篇文章写的还不错,请帮忙转发哈!Thanks♪(・ω・)ノ~~
转载请注明: Vue教程中文网 - 打造国内领先的vue学习网站-vue视频,vue教程,vue学习,vue培训 » vue中计算属性(computed)和方法(methods)的对比