• vue的计算属性computed


    • 可对数据进行逻辑处理
    • 可对数据进行监视

    基础例子

     1 var vm = new Vue({
     2   data: { a: 1 },
     3   computed: {
     4     // 仅读取
     5     aDouble: function () {
     6       return this.a * 2
     7     },
     8     // 读取和设置
     9     aPlus: {
    10       get: function () {
    11         return this.a + 1
    12       },
    13       set: function (v) {
    14         this.a = v - 1
    15       }
    16     }
    17   }
    18 })
    19 vm.aPlus   // => 2
    20 vm.aPlus = 3
    21 vm.a       // => 2
    22 vm.aDouble // => 4

    要理解 computed 的工作原理,只需要理解下面三个问题

    1、computed 也是响应式的

    2、computed 如何控制缓存

    3、依赖的 data 改变了,computed 如何更新

    1.computed 也是响应式的

    给 computed 设置的 get 和 set 函数,会跟 Object.defineProperty 关联起来,所以 Vue 能捕捉到 读取computed 和 赋值computed 的操作。读取computed 时,会执行你设置的 get 函数,但是并没有这么简单,因为还有一层缓存的操作赋值 computed 时,会执行你设置的 set 函数,这个就比较简单,会直接把 set 赋值给Object.defineProperty - set

    2.Computed 如何控制缓存

    我们都知道,computed 是有缓存的,官方已经说明

    "计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值"

    "我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter"

    现在我们要开始讲解,Computed 是如何判断是否使用缓存的

    首先 computed 计算后,会把计算得到的值保存到一个变量中。读取 computed 时便直接返回这个变量。当使用缓存时,就直接返回这个变量。当 computed 更新时,就会重新赋值更新这个变量 

    TIP:computed 计算就是调用 你设置的 get 函数,然后得到返回值

    computed 控制缓存的重要一点是 【脏数据标志位 dirty】,dirty 是 watcher 的一个属性

    当 dirty 为 true 时,读取 computed 会重新计算

    当 dirty 为 false 时,读取 computed 会使用缓存

    1.一开始每个 computed 新建自己的watcher时,会设置 watcher.dirty = true,以便于computed 被使用时,会计算得到值

    2.当 依赖的数据变化了,通知 computed 时,会设置 watcher.dirty = true,以便于其他地方重新渲染,从而重新读取 computed 时,此时 computed 重新计算

    3.computed 计算完成之后,会设置 watcher.dirty = false,以便于其他地方再次读取时,使用缓存,免于计算

    3.依赖的data变化,computed如何更新

    computed 会让 【data依赖】 收集到 【依赖computed的watcher】,从而 data 变化时,会同时通知 computed 和 依赖computed的地方

    原理实现

      1 //标识当前的Dep id
      2 let uidep = 0
      3 class Dep{
      4     constructor () {
      5         this.id = uidep++
      6         // 存放所有的监听watcher
      7         this.subs = []
      8       }
      9 
     10       //添加一个观察者对象
     11       addSub (Watcher) {
     12         this.subs.push(Watcher)
     13       }
     14       //依赖收集
     15     depend () {
     16         //Dep.target 作用只有需要的才会收集依赖
     17         if (Dep.target) {
     18           Dep.target.addDep(this)
     19         }
     20     }
     21 
     22     // 调用依赖收集的Watcher更新
     23     notify () {
     24         const subs = this.subs.slice()
     25         for (let i = 0, l = subs.length; i < l; i++) {
     26           subs[i].update()
     27         }
     28       }
     29 }
     30 
     31 Dep.target = null
     32 const targetStack = []
     33 
     34 // 为Dep.target 赋值
     35 function pushTarget (Watcher) {
     36     if (Dep.target) targetStack.push(Dep.target)
     37       Dep.target = Watcher
     38 }
     39 function popTarget () {
     40   Dep.target = targetStack.pop()
     41 }
     42 /*----------------------------------------Watcher------------------------------------*/
     43 //去重 防止重复收集
     44 let uid = 0
     45 class Watcher{
     46     constructor(vm,expOrFn,cb,options){
     47         //传进来的对象 例如Vue
     48         this.vm = vm
     49         if (options) {
     50           this.deep = !!options.deep
     51           this.user = !!options.user
     52           this.lazy = !!options.lazy
     53         }else{
     54             this.deep = this.user = this.lazy = false
     55         }
     56         this.dirty = this.lazy
     57         //在Vue中cb是更新视图的核心,调用diff并更新视图的过程
     58         this.cb = cb
     59         this.id = ++uid
     60         this.deps = []
     61         this.newDeps = []
     62         this.depIds = new Set()
     63         this.newDepIds = new Set()
     64         if (typeof expOrFn === 'function') {
     65             //data依赖收集走此处
     66               this.getter = expOrFn
     67         } else {
     68             //watch依赖走此处
     69               this.getter = this.parsePath(expOrFn)
     70         }
     71         //设置Dep.target的值,依赖收集时的watcher对象
     72         this.value = this.lazy ? undefined : this.get()
     73     }
     74 
     75     get(){
     76         //设置Dep.target值,用以依赖收集
     77         pushTarget(this)
     78         const vm = this.vm
     79         //此处会进行依赖收集 会调用data数据的 get
     80         let value = this.getter.call(vm, vm)
     81         popTarget()
     82         return value
     83     }
     84 
     85     //添加依赖
     86       addDep (dep) {
     87           //去重
     88           const id = dep.id
     89         if (!this.newDepIds.has(id)) {
     90               this.newDepIds.add(id)
     91               this.newDeps.push(dep)
     92               if (!this.depIds.has(id)) {
     93                   //收集watcher 每次data数据 set
     94                   //时会遍历收集的watcher依赖进行相应视图更新或执行watch监听函数等操作
     95                 dep.addSub(this)
     96               }
     97         }
     98       }
     99 
    100       //更新
    101       update () {
    102           if (this.lazy) {
    103               this.dirty = true
    104         }else{
    105             this.run()
    106         }
    107     }
    108 
    109     //更新视图
    110     run(){
    111         console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
    112         const value = this.get()
    113         const oldValue = this.value
    114         this.value = value
    115         if (this.user) {
    116             //watch 监听走此处
    117             this.cb.call(this.vm, value, oldValue)
    118         }else{
    119             //data 监听走此处
    120             //这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
    121             this.cb.call(this.vm, value, oldValue)
    122         }
    123     }
    124 
    125     //如果计算熟悉依赖的data值发生变化时会调用
    126     //案例中 当data.name值发生变化时会执行此方法
    127     evaluate () {
    128         this.value = this.get()
    129         this.dirty = false
    130     }
    131     //收集依赖
    132     depend () {
    133         let i = this.deps.length
    134         while (i--) {
    135           this.deps[i].depend()
    136         }
    137     }
    138 
    139     // 此方法获得每个watch中key在data中对应的value值
    140     //使用split('.')是为了得到 像'a.b.c' 这样的监听值
    141     parsePath (path){
    142         const bailRE = /[^w.$]/
    143       if (bailRE.test(path)) return
    144           const segments = path.split('.')
    145           return function (obj) {
    146             for (let i = 0; i < segments.length; i++) {
    147                   if (!obj) return
    148                   //此处为了兼容我的代码做了一点修改     
    149                 //此处使用新获得的值覆盖传入的值 因此能够处理 'a.b.c'这样的监听方式
    150                 if(i==0){
    151                     obj = obj.data[segments[i]]
    152                 }else{
    153                     obj = obj[segments[i]]
    154                 }
    155             }
    156             return obj
    157          }
    158     }
    159 }
    160 
    161 /*----------------------------------------Observer------------------------------------*/
    162 class Observer{
    163     constructor (value) {
    164         this.value = value
    165         // 增加dep属性(处理数组时可以直接调用)
    166         this.dep = new Dep()
    167         //将Observer实例绑定到data的__ob__属性上面去,后期如果oberve时直接使用,不需要从新Observer,
    168         //处理数组是也可直接获取Observer对象
    169         def(value, '__ob__', this)
    170         if (Array.isArray(value)) {
    171             //这里只测试对象
    172         } else {
    173             //处理对象
    174               this.walk(value)
    175         }
    176     }
    177 
    178     walk (obj) {
    179         const keys = Object.keys(obj)
    180         for (let i = 0; i < keys.length; i++) {
    181             //此处我做了拦截处理,防止死循环,Vue中在oberve函数中进行的处理
    182             if(keys[i]=='__ob__') return;
    183               defineReactive(obj, keys[i], obj[keys[i]])
    184         }
    185       }
    186 }
    187 //数据重复Observer
    188 function observe(value){
    189     if(typeof(value) != 'object' ) return;
    190     let ob = new Observer(value)
    191       return ob;
    192 }
    193 // 把对象属性改为getter/setter,并收集依赖
    194 function defineReactive (obj,key,val) {
    195       const dep = new Dep()
    196       //处理children
    197       let childOb = observe(val)
    198       Object.defineProperty(obj, key, {
    199         enumerable: true,
    200         configurable: true,
    201         get: function reactiveGetter () {
    202             console.log(`调用get获取值,值为${val}`)
    203               const value = val
    204               if (Dep.target) {
    205                 dep.depend()
    206                 if (childOb) {
    207                       childOb.dep.depend()
    208                 }
    209               }
    210               return value
    211         },
    212         set: function reactiveSetter (newVal) {
    213             console.log(`调用了set,值为${newVal}`)
    214               const value = val
    215                val = newVal
    216                //对新值进行observe
    217               childOb = observe(newVal)
    218               //通知dep调用,循环调用手机的Watcher依赖,进行视图的更新
    219               dep.notify()
    220         }
    221   })
    222 }
    223 //辅助方法
    224 function def (obj, key, val) {
    225   Object.defineProperty(obj, key, {
    226     value: val,
    227     enumerable: true,
    228     writable: true,
    229     configurable: true
    230   })
    231 }
    232 /*----------------------------------------初始化watch------------------------------------*/
    233 //空函数
    234 const noop = ()=>{}
    235 // computed初始化的Watcher传入lazy: true就会触发Watcher中的dirty值为true
    236 const computedWatcherOptions = { lazy: true }
    237 //Object.defineProperty 默认value参数
    238 const sharedPropertyDefinition = {
    239   enumerable: true,
    240   configurable: true,
    241   get: noop,
    242   set: noop
    243 }
    244 // 初始化computed
    245 class initComputed {
    246     constructor(vm, computed){
    247         //新建存储watcher对象,挂载在vm对象执行
    248         const watchers = vm._computedWatchers = Object.create(null)
    249         //遍历computed
    250         for (const key in computed) {
    251             const userDef = computed[key]
    252             //getter值为computed中key的监听函数或对象的get值
    253             let getter = typeof userDef === 'function' ? userDef : userDef.get
    254             //新建computed的 watcher
    255             watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
    256             if (!(key in vm)) {
    257                   /*定义计算属性*/
    258                   this.defineComputed(vm, key, userDef)
    259             }
    260         }
    261     }
    262     //把计算属性的key挂载到vm对象下,并使用Object.defineProperty进行处理
    263     //因此调用vm.somecomputed 就会触发get函数
    264     defineComputed (target, key, userDef) {
    265       if (typeof userDef === 'function') {
    266         sharedPropertyDefinition.get = this.createComputedGetter(key)
    267         sharedPropertyDefinition.set = noop
    268       } else {
    269         sharedPropertyDefinition.get = userDef.get
    270           ? userDef.cache !== false
    271             ? this.createComputedGetter(key)
    272             : userDef.get
    273           : noop
    274           //如果有设置set方法则直接使用,否则赋值空函数
    275             sharedPropertyDefinition.set = userDef.set
    276               ? userDef.set
    277               : noop
    278       }
    279       Object.defineProperty(target, key, sharedPropertyDefinition)
    280     }
    281 
    282     //计算属性的getter 获取计算属性的值时会调用
    283     createComputedGetter (key) {
    284       return function computedGetter () {
    285           //获取到相应的watcher
    286         const watcher = this._computedWatchers && this._computedWatchers[key]
    287         if (watcher) {
    288             //watcher.dirty 参数决定了计算属性值是否需要重新计算,默认值为true,即第一次时会调用一次
    289               if (watcher.dirty) {
    290                   /*每次执行之后watcher.dirty会设置为false,只要依赖的data值改变时才会触发
    291                   watcher.dirty为true,从而获取值时从新计算*/
    292                 watcher.evaluate()
    293               }
    294               //获取依赖
    295               if (Dep.target) {
    296                 watcher.depend()
    297               }
    298               //返回计算属性的值
    299               return watcher.value
    300         }
    301       }
    302     }
    303 }
    View Code

    computed测试:

     1 //1、首先来创建一个Vue构造函数:
     2 function Vue(){
     3 }
     4 //2、设置data和computed的值:
     5 let data={
     6     name:'Hello',
     7 }
     8 let computed={
     9     getfullname:function(){
    10         console.log('-----走了computed 之 getfullname------')
    11         console.log('新的值为:'+data.name + ' - world')
    12         return data.name + ' - world'
    13     }
    14 }
    15 //3、实例化Vue并把data挂载到Vue上
    16 let vue         = new Vue()
    17 vue.data         = data
    18 //4、创建Watcher对象
    19 let updateComponent = (vm)=>{
    20     // 收集依赖
    21     data.name
    22     
    23 }
    24 new Watcher(vue,updateComponent,()=>{})
    25 //5、初始化Data并收集依赖
    26 observe(data)
    27 //6、初始化computed
    28 new initComputed(vue,computed)
    View Code

    在浏览器console中测试:

    1 //首先或的一次getfullname
    2 vue.getfullname
    3 
    4 //第二次调用getfullname看看会有什么变化呢
    5 vue.getfullname
    View Code

     

  • 相关阅读:
    ZYB建围墙
    换根DP
    原码,反码,补码的计算
    字符串游戏-博弈论-trie
    【十连赛day8】神炎皇
    生成序列
    【USACO 2006 February Silver】产奶安排Stall Reservations-贪心
    tar 压缩解压命令
    java 注解
    回溯算法解决全排列问题
  • 原文地址:https://www.cnblogs.com/hzn1995/p/14706038.html
Copyright © 2020-2023  润新知