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的地方
原理实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
computed测试:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
在浏览器console中测试:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //首先或的一次getfullname 2 vue.getfullname 3 4 //第二次调用getfullname看看会有什么变化呢 5 vue.getfullname