Vue中computed分析 在Vue中computed是计算属性,其会根据所依赖的数据动态显示新的计算结果,虽然使用{{}}模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的,在模板中放入太多的逻辑会让模板过重且难以维护,所以对于任何复杂逻辑,都应当使用计算属性。计算属性是基于数据的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值,也就是说只要计算属性依赖的数据还没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数,当然如果不希望使用缓存可以使用方法属性并返回值即可,computed计算属性非常适用于一个数据受多个数据影响以及需要对数据进行预处理的条件下使用。
描述 computed计算属性可以定义两种方式的参数,{ [key: string]: Function | { get: Function, set: Function } },计算属性直接定义在Vue实例中,所有getter和setter的this上下文自动地绑定为Vue实例,此外如果为一个计算属性使用了箭头函数,则this不会指向这个组件的实例,不过仍然可以将其实例作为函数的第一个参数来访问,计算属性的结果会被缓存,除非依赖的响应式property变化才会重新计算,注意如果某个依赖例如非响应式property在该实例范畴之外,则计算属性是不会被更新的。事实上computed会拥有自己的watcher,其内部有个属性dirty开关来决定computed的值是需要重新计算还是直接复用之前的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <!DOCTYPE html > <html > <head > <title > Vue</title > </head > <body > <div id ="app" > </div > </body > <script src ="https://cdn.bootcss.com/vue/2.4.2/vue.js" > </script > <script type ="text/javascript" > var vm = new Vue ({ el : "#app" , data : { a : 1 , b : 2 }, template :` <div> <div>{{multiplication}}</div> <div>{{multiplication}}</div> <div>{{multiplication}}</div> <div>{{multiplicationArrow}}</div> <button @click="updateSetting">updateSetting</button> </div> ` , computed :{ multiplication : function ( ){ console .log ("a * b" ); return this .a * this .b ; }, multiplicationArrow : vm => vm.a * vm.b * 3 , setting : { get : function ( ){ console .log ("a * b * 6" ); return this .a * this .b * 6 ; }, set : function (v ){ console .log (`${v} -> a` ); this .a = v; } } }, methods :{ updateSetting : function ( ){ console .log (this .setting ); this .setting = 3 ; console .log (this .setting ); } }, }) </script > </html >
分析 首先在Vue中完成双向绑定是通过Object.defineProperty()实现的,Vue的双向数据绑定,简单点来说分为以下三个部分:
Observer: 这里的主要工作是递归地监听对象上的所有属性,在属性值改变的时候,触发相应的Watcher。
Watcher: 观察者,当监听的数据值修改时,执行响应的回调函数,在Vue里面的更新模板内容。
Dep: 链接Observer和Watcher的桥梁,每一个Observer对应一个Dep,它内部维护一个数组,保存与该Observer相关的Watcher。
Vue源码的实现比较复杂,会处理各种兼容问题与异常以及各种条件分支,文章分析比较核心的代码部分,精简过后的版本,重要部分做出注释,commit id为0664cb0。 首先在dev/src/core/instance/state.js中定义了初始化computed以及initComputed函数的实现,现在暂不考虑SSR服务端渲染的computed实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 export function initState (vm : Component ) { vm._watchers = [] const opts = vm.$options if (opts.props ) initProps (vm, opts.props ) if (opts.methods ) initMethods (vm, opts.methods ) if (opts.data ) { initData (vm) } else { observe (vm._data = {}, true ) } if (opts.computed ) initComputed (vm, opts.computed ) if (opts.watch && opts.watch !== nativeWatch) { initWatch (vm, opts.watch ) } } function initComputed (vm : Component , computed : Object ) { const watchers = vm._computedWatchers = Object .create (null ) const isSSR = isServerRendering () for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env .NODE_ENV !== 'production' && getter == null ) { warn ( `Getter is missing for computed property "${key} ".` , vm ) } if (!isSSR) { watchers[key] = new Watcher ( vm, getter || noop, noop, computedWatcherOptions ) } if (!(key in vm)) { defineComputed (vm, key, userDef) } else if (process.env .NODE_ENV !== 'production' ) { if (key in vm.$data ) { warn (`The computed property "${key} " is already defined in data.` , vm) } else if (vm.$options .props && key in vm.$options .props ) { warn (`The computed property "${key} " is already defined as a prop.` , vm) } } } }
defineComputed传入了三个参数,vm实例、计算属性的key以及userDef计算属性的定义,属性描述符sharedPropertyDefinition在初始化定义之后经过userDef和shouldCache等多重判断后被重写,进而通过Object.defineProperty(target, key, sharedPropertyDefinition)进行属性的定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 const sharedPropertyDefinition = { enumerable : true , configurable : true , get : noop, set : noop } export function defineComputed ( target : any, key : string, userDef : Object | Function ) { const shouldCache = !isServerRendering () if (typeof userDef === 'function' ) { sharedPropertyDefinition.get = shouldCache ? createComputedGetter (key) : createGetterInvoker (userDef) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter (key) : createGetterInvoker (userDef.get ) : noop sharedPropertyDefinition.set = userDef.set || noop } if (process.env .NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function ( ) { warn ( `Computed property "${key} " was assigned to but it has no setter.` , this ) } } Object .defineProperty (target, key, sharedPropertyDefinition) }
参考 1 2 3 4 5 6 https://cn.vuejs.org/v2/api/#computed https://juejin.im/post/6844903678533451783 https://juejin.im/post/6844903873925087239 https://cn.vuejs.org/v2/guide/computed.html https://zheyaoa.github.io/2019/09/07/computed/ https://www.cnblogs.com/tugenhua0707/p/11760466.html