【 Vue全家桶 · Vue(五)】Vue框架中的监听机制之一 —— 计算属性computed


🏆 PART ONE 🏆

如果你已经对计算属性computed有过初步的了解,建议直接阅读 第二部分 👈


一. 通过methods引入computed的概念

之前我们讲插值的时候说:双大括号里可以放简单的JavaScript表达式。当时有个列子:

<div id="app">
    <div>原值 —— {{msg}}</div>
    <div>表达式反转 —— {{msg.split('').reverse().join('')}}</div>
</div>
new Vue({
    el:'#app',
    data:{
        msg:'hello world!'
    }
})

在这里插入图片描述

1.1 methods版本

这样会在页面上显示反转之后的字符串。这样的需求还比较简单,如果我们的需求再复杂一些,比如还想做到字符去重,不分大小写排序等等效果,就不适合直接写在模板中了。

一般的操作是去组件中定义一个方法函数去专门处理字符串,然后把结果返回,渲染到视图中:

<div id="app">
    <div>原值 —— {{msg}}</div>
    <div>表达式反转 —— {{msg.split('').reverse().join('')}}</div>
    <div>方法反转 —— {{revMsg()}}</div>
</div>
new Vue({
    el:'#app',
    data:{
        msg:'hello world!'
    },
    methods:{
        revMsg:function(){
            return this.msg.split('').reverse().join('')
        }
    }
})

在这里插入图片描述

1.2 computed版本

上述场景中计算属性(computed) 无疑是更优的选择!先来看上例的computed版本:

<div id="app">
    <div>原值 —— {{msg}}</div>
    <div>表达式反转 —— {{msg.split('').reverse().join('')}}</div>
    <div>方法反转 —— {{revMsg()}}</div>
    <div>计算属性 —— {{reverseMsg}}</div>
</div>
new Vue({
    el:'#app',
    data:{
        msg:'hello world!'
    },
    computed:{
        reverseMsg:function(){
            return this.msg.split('').reverse().join('')
        }
    },
    methods:{
        revMsg:function(){
            return this.msg.split('').reverse().join('')
        }
    }
})

在这里插入图片描述


❀拓展一下❀

方法是在计算属性之后执行,同名方法会被覆盖。建议书写顺序为:data,computed,methods


看到这里,好像方法和计算属性没啥区别…

别着急,我们继续在方法和计算属性中都加入打印语句,再来让他们分别都执行三次看看:

<div id="app">
    <div>原值 —— {{msg}}</div>
    <div>表达式反转 —— {{msg.split('').reverse().join('')}}</div>
    <div>方法反转 —— {{revMsg()}}</div>
    <div>方法反转 —— {{revMsg()}}</div>
    <div>方法反转 —— {{revMsg()}}</div>
    <div>计算属性 —— {{reverseMsg}}</div>
    <div>计算属性 —— {{reverseMsg}}</div>
    <div>计算属性 —— {{reverseMsg}}</div>
</div>
new Vue({
    el:'#app',
    data:{
        msg:'hello world!'
    },
    computed:{
        reverseMsg:function(){
            console.log("计算属性"); 
            return this.msg.split('').reverse().join('')
        }
    },
    methods:{
        revMsg:function(){
            console.log("方法");
            return this.msg.split('').reverse().join('')
        }
    }
})

在这里插入图片描述
这下可以看到区别了吧!方法执行了三次,计算属性只执行了一次 ?而且他们做到的事情是一样的,毫无疑问,使用计算属性computed的方案性能更好。

其实页面上计算属性后两次的结果都是从缓存中拿到的。这就是他俩最大区别,也是计算属性的原理所在。那么我们再来添加一个created生命周期钩子函数来每隔一秒改变一次msg的值,再来看看计算属性的表现:

created(){
    setInterval(()=>{
        this.msg += 1
    },1000)
}

在这里插入图片描述
可以看到每次修改msg的值,都执行了三次方法和一次计算属性,这是因为计算属性会自动监测数据源的改变:只有数据源发生变化才会重新执行计算属性,否则会返回缓存中的上次的计算结果。

相较于每次调用都会执行函数的methods方法,计算属性的这个特性无疑是可以减少我们很多没有意义的操作。


🏆 PART TWO 🏆


二. computed全面解读

2.1 computed本质

计算属性computed本质上是vue实例对象的一个属性,集中定义在computed:{}中。

2.1.1 调用方式

<div id="app">
    <div>原值 —— {{msg}}</div>
    <div>方法反转 —— {{revMsg()}}</div>
    <div>计算属性 —— {{reverseMsg}}</div>
</div>

计算属性computed的调用方式属于属性调用(类比data:{}中定义的属性):
可以通过实例对象直接调用,vm.reverseMsg;或者通过双大括号把计算结果插入到视图模板中:{{reverseMsg}}

methods:{}中定义的方法在调用方式上属于函数调用,必须加上() —— {{revMsg()}}

2.1.2 完整写法(setter方法)

平常我们见到的计算属性的形式都很有迷惑性,往往会被误认为是函数,其实那只是默认没有setter方法,只有getter方法的计算属性的只读形式。

computed:{
    reverseMsg:function(){
        return this.msg.split('').reverse().join('')
    }
}

其实真正的它长下边这个样子 —— 完整写法 —— 可读可写形式:

computed:{
    reverseMsg:{
        // getter 可读
        get:function(){
            return this.msg.split('').reverse().join('')
        },
        // setter 可写
        set:function(val){}
    }
}

联想一下之前我们讲的Object.defineProperty中的存取描述符形式(这就不难理解了吧 ~ )

var computed = {};

Object.defineProperty(computed,"reverseMsg",{
    get:function(){},
    set:function(val){}
    
    configurable:true, 
    enumerable:true
});

所以我们也可以提供一个 setter 方法,实现常见的属性赋值操作:

computed:{
    reverseMsg:{
        // getter 可读
        get:function(){
            return this.msg.split('').reverse().join('')
        },
        // setter 可写
        set:function(val){
            this.msg = val
        }
    }
}

在这里插入图片描述

2.2 响应式依赖

data:{
    num:0
},
computed:{
    funcNum:function(){
        return this.num + 1
    }
}

参与计算属性计算过程的现有实例数据属性就被称为响应式依赖。即定义在data:{}中的数据属性。对应到计算属性的代码块中就是this.xxx格式的数据。(比如上边例子里的this.num)

对应的其他格式数据就不是响应式数据,比如下例中的日期数据Date.now()

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

❀拓展一下❀

有this的地方总会提到那个特殊的存在 —— 箭头函数。切记计算属性中不可用箭头函数,会使this指向混乱。


2.3 computed作用原理

完成一次计算并返回结果。 参与计算的响应式依赖可以是一个或多个,只要其中某一个响应式依赖发生改变都会重新计算;没有依赖改变之前调用该计算属性都会直接从缓存中拿到上次的计算结果并返回。

2.4 computed适用场景

计算属性的监听适用于多对一的场景,即某个组件内的一个数据会受多个数据的影响。
在这里插入图片描述


❀ 拓展一下❀

区别于侦听器watch的一对多场景,有关侦听器watch的讲解请看下一篇:

【Vue学习笔记 · 基础篇(六)】Vue框架中的监听机制之二 —— 侦听器watch


vue.js的设定中并不限制响应式依赖的个数和数据类型,因此根据参与计算的响应式依赖的个数和具体数据类型,让计算属性可以适用于以下场景:

2.4.1 多数据监听

当一个计算属性中参与运算的响应式依赖的个数有多个时,引擎会同时全部监听。 只要其中某一个响应式依赖发生改变都会重新计算。

<div>{{add}}</div>
data:{
    num1:0,
    num2:2
},
computed:{
    add:function(){
        return this.num1 + this.num2
    }
}

这个例子中,参与计算的响应式依赖有this.num1this.num2,我们任意改变其中一个的值,都会重新计算,看下图的演示:

在这里插入图片描述

2.4.2 深度监听

当参与计算的响应式依赖为一个对象时,默认进行的是深度监听。 只要对象内某个属性发生变化就会重新计算。

可以来看个案例 —— 全选操作(完整代码文末有附):

在这里插入图片描述

<div id="app">
    <div>
        <span v-if="isAllCheck" style="color:red;">已全选</span>
        <span v-else>全选</span>
    </div>
    <br>
    <div v-for="item in dataList">
        <input type="checkbox" v-model="item.isCheck"/>{{item.name}}
    <div>
</div>

列表中每一项的选择与否都会决定全选状态的是否成立。(典型的多对一场景)

data:{
    dataList:[
        {isCheck:false, name:'选项一'},
        {isCheck:false, name:'选项二'},
        {isCheck:false, name:'选项三'}
    ]
}

这里我们就需要监听每一个选项的isCheck值,使用计算属性isAllCheck来监听的话,由于默认是深度监听,所以直接监听 this.dataList数组这个响应式依赖即可。

computed:{
    isAllCheck: function(){
        const dataList = this.dataList;
        for(let item of dataList){
            // 有未选中的item就直接return false
            if(!item.isCheck) return false;	
        };
        // 全都选中才return true
        return true;
    }
}

❀ 拓展一下❀

完善一下全选这个demo:添加一个全选的选择框,并绑定事件UnSelectAll,完成一键全选或取消全选的功能:

在这里插入图片描述

<input type="checkbox" v-model="isAllCheck" v-on:click="UnSelectAll"/>
methods:{
    UnSelectAll:function(){
        const dataList = this.dataList;
        if(this.isAllCheck){
            var prop = false
        }else{
            var prop = true
        };
        for(let item of dataList){
            item.isCheck = prop;	
        };
    }
}

最后附上全选demo的全部代码,大家可以直接CV来感受一下用计算属性computed来实现监听的便利吧!

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>demo-select-all</title>
<script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.min.js"></script>
</head>
 
<body>
<div id="app">
    <div>
        <input type="checkbox" v-model="isAllCheck" v-on:click="UnSelectAll"/>
        <span v-if="isAllCheck" style="color:red;">已全选</span>
        <span v-else>全选</span>
    </div>
    <br>
    <div v-for="item in dataList">
        <input type="checkbox" v-model="item.isCheck"/>{{item.name}}
    <div>
</div>
<script>
    new Vue({
        el:'#app',
        data:{
            dataList:[
                {isCheck:false, name:'选项一'},
                {isCheck:false, name:'选项二'},
                {isCheck:false, name:'选项三'}
            ]
        },
        computed:{
            isAllCheck: function(){
                const dataList = this.dataList;
                for(let item of dataList){
                    //如果item中存在isCheck为false的直接return false;
                    if(!item.isCheck) return false;	
                };
                return true;
            }
        },
        methods:{
            UnSelectAll:function(){
                const dataList = this.dataList;
                if(this.isAllCheck){
                    var prop = false
                }else{
                    var prop = true
                }
                for(let item of dataList){
                    item.isCheck = prop;	
                };
            }
        }	
    });
</script>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值