- methods
- watch
- computed
- 关于方法、监听器、计算属性的使用和适应场景
一、methods-方法
在数据渲染是需要基于多个数据时第一种方法,可以采用methods属性中的方法计算返回值来实现,先来看示例:
1 <div id="example">{{describe()}}</div> 2 <script> 3 var vm = new Vue({ 4 el:"#example", 5 data:{ 6 name:'他乡踏雪', 7 looks:'beautiful', 8 // describe:'他乡踏雪:beautiful' -- 注意data中的命名不能与methods中的命名一致,会发生冲突 9 // 渲染数据时会先取data中的值,即便在表达式中写入的是方法执行也会取data中的数据(采用正则匹配)相当于字符串做方法执行:报错 10 }, 11 methods:{ 12 describe(){ 13 return this.name + this.looks; 14 } 15 } 16 }); 17 </script>
methods并且可以实现当数据发生变化时,或者在相关表达式被被执行时(比如示例中),methods就会触发执行, 但是methods还有一种情况就暴露了他的缺点,来看下面的示例:
<div id="example">{{describe()}}{{age}}</div>
如果结构是这样的情况,methods中的describe在age发生变化时是不应该执行的,但是他却会执行,所以methods的执行不只是在数据变化和表达式执行时,而且还会在DOM内部分结构重新渲染时也会触发执行。这种情况就产生了额外的计算,影响性能。
二、watch-侦听器
基于对methods渲染触发问题的思考,vue还提供了一个侦听器来实现只有相关数据发生变化时才触发执行的watch,来看示例:
1 <div id="example">{{describe}}{{age}}</div> 2 <script> 3 var vm = new Vue({ 4 el:"#example", 5 data:{ 6 name:'他乡踏雪', 7 looks:'beautiful', 8 describe:"他乡踏雪 beautiful", 9 age:18 10 }, 11 watch:{ 12 name(){ 13 this.bescribe = this.name + " " + this.looks 14 }, 15 looks(){ 16 this.bescribe = this.name + " " + this.looks 17 } 18 } 19 });
侦听器watch就是在watch属性中定义与data中数据同名的方法,当data中的某个数据发生变化时,watch中对应的侦听方法就会触发执行。所以使用watch的侦听机制就不会发生像methods那样非相关数据触发的渲染也会导致方法执行,所以从这个角度来看,watch比methods的性能要好些。
三、computed-计算属性
watch和methods都存在一个类似的问题,就是代码冗余,vue还提供了一个解决方案,就是计算属性computed,看看通过计算属性computed改良上面的示例代码:
1 <div id="example">{{describe}}{{age}}</div> 2 <script> 3 var vm = new Vue({ 4 el:"#example", 5 data:{ 6 name:'他乡踏雪', 7 looks:'beautiful', 8 age:18 9 }, 10 computed:{ 11 describe(){ 12 return this.name + " " + this.looks; 13 } 14 } 15 }); 16 </script>
使用计算属性可以直接将属性名用于结构表达式中渲染数据,免去了data中的重复定义数据,并且也只有在相关的数据发生变化时才会被触发,免去了watch中多个数据情况下重复监听。
计算属性computed的功能不只是用来实现计算数据渲染,还可以实现data数据的写入,其实上面的示例只是vue实例的getter方法,所以上面的示例代码可以改写成下面这样:
1 <div id="example">{{describe}}{{age}}</div> 2 <script> 3 var vm = new Vue({ 4 el:"#example", 5 data:{ 6 name:'他乡踏雪', 7 looks:'beautiful', 8 age:18 9 }, 10 computed:{ 11 describe:{ 12 get(){ 13 return this.name + " " + this.looks; 14 } 15 } 16 } 17 }); 18 </script>
再来看看setter方法,所谓计算属性的setter方法就是当设置describe的值时,name和looks的值也会发生变化,具体来看示例代码:
1 <div id="user2" > 2 <p v-on:click="changeName">{{userName}}</p> 3 <p>looks:{{looks}}</p> 4 <p v-on:click="changAge">age:{{userAge}}</p> 5 <p>点击昵称切换昵称和描述,并打印:{{describe}}</p> 6 </div> 7 <script> 8 let user2 = new Vue({ 9 el:'#user2', 10 data:{ 11 userName:'南都谷主', 12 userAge:18, 13 looks:'beautiful' 14 15 }, 16 methods:{ 17 changeName(){ 18 // this.userName = this.userName == '南都谷主' ? '他乡踏雪' : '南都谷主'; 19 this.describe = this.userName == '南都谷主' ? '他乡踏雪 ugly' : '南都谷主 beautiful'; 20 21 }, 22 changAge(){ 23 this.userAge = this.userAge == 18 ? ++this.userAge : --this.userAge; 24 } 25 }, 26 watch:{ 27 userName(){ 28 this.userAge = this.userAge == 18 ? ++this.userAge : --this.userAge; 29 } 30 }, 31 computed:{ 32 describe:{ 33 get(){ 34 return this.userName + ' ' + this.looks; 35 }, 36 set(v){ 37 console.log('触发了set:' + v); 38 const valuse = v.split(' '); 39 this.userName = valuse[0]; 40 this.looks = valuse[1]; 41 } 42 } 43 } 44 }); 45 </script>
也就是说计算属性是可读可写的,还有就是computed有一个非常重要的特性就是当数据不发生变化时不会执行getter方法,这对于性能优化有很大的帮助,比如computed中的一个属性依赖多个数据渲染,但是在很多时候只会有部分数据发生变化,这时候是需要对发生变化的数据执行getter方法读取新的数据来实现渲染,其他的数据直接去缓存数据。
但是这也有一个闭端,就是有时候依赖的数据并非观察数据(非vue实例上的数据),但又要时时更新,比如下面这个示例:
1 get(){ 2 return this.name + " " + this.looks + ' ' + Date.now(); 3 }
当name和looks发生变化时,Date.now()始终渲染的是初始数据,不会更新,针对这种情况,vue在计算属性对象中指定了cache字段来控制是否开启缓存,cache的值为false时关闭缓存,会对每个依赖数据执行getter方法,cache默认为true表示读取缓存。所以上面的代码可以修正为:(这个关闭缓存好像失效了,待进一步测试确认)
1 //关闭缓存 2 cache:false, 3 get(){ 4 return this.name + " " + this.looks + ' ' + Date.now(); 5 }
到了这里你是不是感觉computed有点过分完美,其实不然,计算属性也是存在缺陷的,比如计算属性的节点被移除,并且模板中其他地方没有在引用该属性的时候,那么这个getter方法就不会执行,比如下面这个示例:(来源vue.js权威指南55页)
1 <div id="example"> 2 <button @click = 'toggleShow'>Toggle Show Total Price</button> 3 <p v-if = "showTotal">Total Price = {{totalPrice}}</p> 4 </div> 5 <script> 6 var vm = new Vue({ 7 el:"#example", 8 data:{ 9 showTotal:true, 10 basePrice:100, 11 sum:0 12 }, 13 computed:{ 14 totalPrice(){ 15 console.log(this.sum++); 16 return this.basePrice; 17 } 18 }, 19 methods:{ 20 toggleShow(){ 21 this.basePrice = this.basePrice + 1; 22 this.showTotal = !this.showTotal; 23 } 24 } 25 }); 26 </script>
在实例中,观察控制台的sum输出必须是在p标签显示的时候才会执行,并且sum的累加比basePrice累加要慢一倍。也就是证实了当计算属性computed的节点被移除后,并且模板中没有其他地方在引用的话,getter方法就不会执行。
要想他执行的话可以采用以下方法:
<div id="example"> <button @click = 'toggleShow'>Toggle Show Total Price</button> <p>{{totalPrice}}</p> <p v-if = "showTotal">Total Price = {{totalPrice}}</p> </div>
只能是当计算属性一直出现在模板中,getter方法才会被执行。
四、关于方法、监听器、计算属性的使用和适应场景分析
4.1看似methods、watch、computed都是用函数的形式定义,其实有本质上的区别,先从名称上来分析它们分别为方法、监听器、计算属性,首先要搞清楚它们三者都是为Vue对象服务的,也就是说methods是Vue实例的方法、watch是Vue实例数据的监听器、computed是Vue的计算属性,通过来描述理解就会豁然开朗了,用面向对象的思想继续来思考:
methods方法就是对象用来操作数据的函数,所以methods可以被用于Vue对象管理的模块上的事件调用、使用方法的返回值可以直接用来渲染数据。
watch监听器就是对象用来监听数据变化的,所以定义在data中的数据只要发生变化就会触发对应的监听器。
computed计算属性就是对象的动态数据,它需要通过相关的行为来触发数据的更新,但一定要注意它本质上就是数据,而不能把它当作方法来看待,能触发它更新数据的相关行为当然就是与它相关的数据(vue数据驱动的本质),所以只要被它包含在get中的数据就会驱动它执行get方法刷新自身。相对应数据本身自己也会发生变化,所以直接对computed进行赋值操作就会触发它的set方法,所以通过set方法也可以更新其他数据。到这里相信关于computed数据不发生变化时它不会触发get渲染也不会触发set更新其他数据的底层逻辑了,因为它本身就是数据,数据不发生变化为什么要触发渲染,数据不发生变化为什么要触发set呢?很多解释关于compute都是说它会缓存数据,难道data中的数据不缓存数据吗?data中的数据不发生变化会驱动渲染吗?
4.2至于它们的适应场景,很多人会提到将methods的返回值作为数据直接渲染到页面会因为其他数据的刷新而跟着被刷新,试问vue对象的数据发生变化了,是不是要驱动页面渲染,而页面渲染使用了methods的返回这时候不刷新行吗?因为渲染本身要使用到它的返回值,而不是存在data中的数据。所以请问将返回值作为渲染数据合适吗?这时候肯定是使用计算属性呀。
4.3关于catch监听器,你可以把它看作是data中数据的set行为,个人认为它同等与computed的set。
4.5再回头来总体看看,从面向对象的角度来说,方法与属性是在同一个级别上,它们有同等的地位,Vue实例的方法是methods,属性就是data和computed,而catch是为data中的数据服务,所以这三者并非同一个物种,不要看着它都可以使用函数的方式定义就认为它们是一个级别的公民,Vue实例手握皇权、data是它的国库里的珠宝,methods是它的差役可以在它的指挥下使用国库里的珠宝,catch是珠宝使用的后续治理江山的效益的蝴蝶效应。而computed则是它的灵宠,它既可以是财宝也可以是差役,也可以授权给差役使用。差役、收益效应、灵宠会是在同一个地位上吗?