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

 

原文地址:https://www.cnblogs.com/hzn1995/p/14706038.html