计算属性computed

computed函数接收两个参数(get函数与set函数),调用computed函数可创建一个ComputedRefImpl对象(内含value、_value、_dirty等属性与函数)。
computed的参数get函数体内放置依赖对象与被读时要返回的值或计算式的代码。
参数set函数接收写入值,函数体内放置value属性被改写时的较验、加工、生效代码。
computed函数创建对象如下:

<template>
  <h1>{{wow}}</h1>
  <h1>{{obj}}</h1>
  <button @click="ee">改变依赖</button>
</template>
<script setup>
import { reactive,ref ,computed} from 'vue';
   let obj =ref({eee:9})
   let wow=computed(()=>{return obj.value.eee+1})
   function ee(){obj.value.eee=98, console.log(wow,obj)}
</script>

杂谈响应原理
响应式对象能使DOM响应更新,是因为响应式对象会收集依赖自已的DOM名单,当自已改变时,会依名单通知DOM重新读取自已。

一、computed的get

1、computed函数的书写规范:computed({get与set对象})

  • 参数是对象,注意要用{}括起:computed({get(){},set(){}})
  • 只有get:通常我们只用到get,可以省略set:computed({get(){}})。
  • 只有get时可以匿名,匿名要去掉{}:setcomputed(()=>{})

2、computed内部原理

  • a、名单收集:
    computed​会将所有读取computed的DOM对象与computed对象收入到名单列表里。
  • b、名单并入响应式对象的依赖:
    computed​会将名单及自已并入给get内部的响应式对象的依赖名单内。
  • c、响应式对象双重告知:
    当get内部的响应式对象发生改变时,会依名单告知所有依赖(告知读取computed的DOM对象及告知computed对象)。(注意computed不是响应式对象,computed虽有名单但并不会告知DOM对象)。
  • d、computed收到告知处理:
    当computed收到告知时,会将_dirty属性赋值为true。
    computed初始化时为_dirty=true,computed被读取后_ dirty=fales。DOM初始渲染时会读取computed,因此DOM渲染完成后_ dirty=fales。
  • e、DOM对象收到告知处理:
    DOM对象收到告知,而是先检查_dirty属性,当_dirty=fales时读取value属性值;当_dirty=true时会运行get函数,get函数返回值赋给value属性值,将_dirty=fales、读取value属性值。
    因此_ dirty属性直接读取value缓存值还是运行get函数后再读取value值,当有大量读取时,能大大节省计算资源。
    如下强行使_dirty=true,以致于每次读取时都会运行get函数。

    <template>
    <h1 > {{wow}} </h1>
    <button @click="ee">测试_dirty=fales不会计算</button>
    <button @click="ef">测试_dirty=true会计算</button>
    </template>
    <script setup>
    import { reactive,ref ,computed} from 'vue';
    let obj =ref({eee:1})
    let wow=computed(()=>{console.log('我计算了'); {return obj.value.eee}})
    function ee(){console.log(wow.value)}
    function ef(){wow._dirty=true,console.log(wow.value)} 
    </script>

3、接受告知与被读取非同步

通常computed接受依赖告知时,dom也会收到告知对computed进行读取,但computed接受告知与被读取并不是同步的。
这是因为DOM更新机制(在一个作用域中,DOM更新总是在最后执行),依赖向computed发出告知后,还会执行作用域的其它代码,直到所有代码执行完毕才会执行DOM的computed读取。
多次告知一次读取: 因告知与被读取非同步,当一个作用域中有多次告知,但DOM的computed读取只在最后读取一次。
每告必读的实现: 如果要每次告知必须被读取一次,可在作用域内触发告知之后放置立即读取computed的代码。
如下:在wocao.na++发出告知后立即放置console.log(mabi.value)立即读取代码。使原本计算一次变成了计算两次。

<template>
  <span>{{mabi}}</span><br>
  <button @click="pp">侦听源一</button>
</template>
  <script setup>
  import { reactive,computed } from 'vue' 
  var eee=0
  const wocao = reactive({name:0,na:2})
  const mabi = computed(() => {console.log('计算一次');if(wocao.na>0){eee++;  return wocao.name+eee}})
  const pp=function(){wocao.na++,console.log(mabi.value),wocao.na++}
</script>

通常一次读取与每告必读的计算结果是一样的,但如果计算式内有自增减的普通变量,每次读取都会返回不同值,所在每告必读只在特别的情况下才使用。

4、简单计算与条件计算

  • 简单计算
    直接返回计算值的计算,如()=>{return a},return前不加判断语句。
    return依赖: 跟在return后的计算式中的响应式对象称为return依赖。
    get依赖: 出现在get里,但在return之外,并不参与计算的响应式对象称为get依赖。
    无依赖return: 跟在return后的计算式中无响应式对象,称为无依赖return。无依赖return不会触发告知,需由get依赖触发告知。
    如下:

    <template>
    <h1 >wow值: {{wow}} </h1>
    <button @click="ee">改变return依赖</button>
    <button @click="ef">改变return的普通变量</button>
    <button @click="ed">改变get依赖</button>
    </template>
    <script setup>
    import { reactive,ref ,computed} from 'vue';
    let obj =ref('return依赖')
    let ob =ref('get依赖')
    var uu='return后的普通变量'
    let wow=computed(()=>{console.log(ob.value,'我计算了');return obj.value+'+'+uu})
    function ee(){obj.value=obj.value+0}
    function ef(){uu+=0}
    function ed(){ob.value+=0}
    </script>
  • 条件计算
    在计算式前加入判断条件的计算叫条件计算,如()=>{if(uu){return a}}。
    条件计算除了return依赖、get依赖​外还有一种新的依赖,叫条件依赖。
    条件依赖: 条件判断语句内的响应式对象称条件依赖。响应式对象更改会触发双重告知执行get。
    多分支计算: 如采用if(){}else{}或if(){}else if(){}else{}语句会产生多分支计算,执行get时依条件执行其中一支。
    条件组: 每种条件与其后的计算称为条件组。分为有依赖条件组(条件中有条件依赖)、无依赖条件组(条件中无条件依赖)。
    当前条件组: 最后一次执行get依条件执行的那一支为当前条件组
    顶级依赖: get依赖、条件依赖都是顶级依赖,顶级依赖改变时,会触发双重告知执行get。
    次级依赖: return依赖为次级依赖。只有当前条件组的次级依赖才会触发告知。
    如下,非当前条件组的依赖变化不会触发告知:

    <template>
    <h1 >wowo值: {{wowo}} </h1>
    <button @click="ef">改变当前条件计算组</button>
    <button @click="ee">次级依赖变化</button>
    </template>
    <script setup>
    import { reactive,ref ,computed} from 'vue';
    let obj =ref(1)
    var uu=ref(true)
    let wowo=computed(()=>{console.log('计算了一次');if(uu.value){return obj.value}else{return'条件为假计算组'}})
    function ee(){obj.value++,console.log(obj.value,wowo._dirty)}
    function ef(){uu.value=!uu.value}
    </script>

    条件组切换: 有依赖条件组条件改变时,会触发执行get,能自动的切换到符合条件的条件组。无依赖条件组的条件改变时,不会触发执行get,需通过其内的次级依赖或外部的get依赖才能切换到符合条件的条件组。
    如下是无依赖条件组利用其内的次级依赖和外部get依赖切换条件组的实例:

    <template>
    <h1 >wowo值: {{wowo}} </h1>
    <button @click="ef">点击:条件{{obb}}</button><br>
     <h4 >当前条件组为{{ui}},请点击改变下面{{ui}}条件组的次级依赖</h4>
    <button @click="er">true条件组的次级依赖</button>
    <button @click="ee">false条件组的次级依赖</button><br>
    请点击改变下面的顶级依赖触发告知<br>
    <button @click="eer">我是get依赖</button>
    </template>
    <script setup>
    import { reactive,ref ,computed} from 'vue';
    let obj =ref(1)
    let ob =ref(false)
    let oob =ref(false)
    let obb =ref('未改变')
    var uu=true
    var ui=uu
    let wowo=computed(()=>{ui=uu;oob.value;if(uu){return obj.value}else{return ob.value}})
    function ef(){uu=!uu,obb.value='已改变'}
    function er(){obj.value++,obb.value='未改变'}
    function ee(){ob.value=!ob.value,obb.value='未改变'}
    function eer(){oob.value=!oob.value,obb.value='未改变'}
    </script>

5、依赖不能自增减

get内的依赖自增减,会导致计算再运行一次。后台会提示“计算仍然是脏的”

二、computed的set

computed是通过计算返回值赋给value属性,value值为computed被读取值,绕开计算直接赋值value会使计算失去意义,因此默认value属性只读,直接赋值value后台会警示“computed只读”。

1、set开启可写并拦截

set函数可开启value属性可写,并对写操作(赋值)进行拦截。
书写规范:set(newV){}
对value属性写入操作会向set函数传递一个参数,此参数为value属性写入的值。

2、set的功能

a、写操作生效

set开启了value属性可写,但任何对value属性的写操作(赋值)都会被set拦截。要使value属性写操作生效,需在set内对拦截值进行处理,以间接的方式使value属性等于要改写的值。这种使写操作生效的方法称为写操作生效。
使写操作生效的方法​有两种,分别为_value属性全等法与依赖传导法。

  • _value属性全等法: 利用_value属性与value属性全等特性,给_value赋value写操作值间接实现value写操作生效。
    代码如下:set(newV){this._value=newV}
    因computed不是响应对象,value属性值改变时不会告知DOM更新读取,因此要在写操作后使用绑定key属性来更新DOM
  • 依赖传导法: 利用依赖来触发get计算返回值给value属性的方法,曲线实现写操作生效。
    如果get的return后不是一个计算式而是单一的一个响应式对象,代码如下:set(newV){依赖=newV};如果return后是计算式,需要对计算式进行反算,代码如下:set(newV){依赖=newV反算式}
    两种方式实例如下:

    <template>
    <span :key="woca">{{mabi}}</span><br>
    <button @click="pp">通过_value使写生效</button>
    <button @click="pf">通过依赖使写生效</button>
    </template>
    <script setup>
    import { ref,computed } from 'vue' 
    const wocao = ref(1)
    const woca = ref(true)
    const mabi = computed({
     get() {console.log('我计算了') ;return wocao.value+1 },
     set(newV) {if(newV==8){this._value=newV}else if(newV==9){wocao.value=newV-1} }})
    const pp=function(){mabi.value=8,woca.value=!woca.value} 
    const pf=function(){mabi.value=9} 
    </script>
b、写较验与写加工

可以在set内对写的值进行较验或计算后,再通过_value属性全等法与依赖传导法返回写操作值。

c、_ value

_ value在不开启可写(无set)的情况下也可间接改变value的值。即不用在set函数内也能通过_value属性更改value属性值。

三、computed与普通函数的区别

在DOM中可以使用普通函数摸拟computed函数,实现computed函数​的get功能(无法摸拟set功能),方法是在普通函数内书写get内的条件计算代码部分。

  • 方法可以使用条件依赖与赖,但不能使用get依赖
  • 方法被读取时没有缓存 每次读取都会进行运算。
  • 方法读取时要的名称后加()小括号

百分之一百零八
15 声望3 粉丝