Vue 2.0 深入源码分析 基础篇 computed 属性详解
Posted greatdesert
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue 2.0 深入源码分析 基础篇 computed 属性详解相关的知识,希望对你有一定的参考价值。
用法
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护,比如:
<div id="example"> message.split(‘‘).reverse().join(‘‘) </div> <script> var app = new Vue( el:‘#example‘, data:message:‘hello world‘ ) </script>
这样模板不再是简单的声明式逻辑,必须看一段时间才能意识到,对于这些复杂逻辑,需要使用计算属性,例如:
<div id="example"> reversedMessage</div> <script> var app = new Vue( el:‘#example‘, data:message:‘hello world‘, computed: reversedMessage:function()return this.message.split(‘‘).reverse().join(‘‘) ) </script>
在模板中可以把computed当作data属性来使用
computed是一个对象,每个键是计算属性的值,值有两种使用方法:值是一个函数,或者值是一个包含get和set的对象
源码分析
Vue实例后会先执行_init()进行初始化(4579行)时,会执行initState()进行初始化,如下:
function initState (vm) //第3303行 vm._watchers = []; var 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 /* asRootData */); if (opts.computed) initComputed(vm, opts.computed); //如果定义了computed,则调用initComputed初始化computed if (opts.watch && opts.watch !== nativeWatch) initWatch(vm, opts.watch);
initComputed函数如下:
var computedWatcherOptions = lazy: true; //计算属性的配置信息 function initComputed (vm, computed) //第3424行 // $flow-disable-line var watchers = vm._computedWatchers = Object.create(null); //定义一个空对象,没有原型的,用于存储所有计算属性对应的watcher // computed properties are just getters during SSR var isSSR = isServerRendering(); for (var key in computed) //遍历每个计算属性 var userDef = computed[key]; //将计算属性的值保存到userDef里面 var getter = typeof userDef === ‘function‘ ? userDef : userDef.get; //如果userDef是一个函数则赋值给getter,否则将userDef.get赋值给getter if ("development" !== ‘production‘ && getter == null) warn( ("Getter is missing for computed property \\"" + key + "\\"."), vm ); if (!isSSR) // create internal watcher for the computed property. watchers[key] = new Watcher( //创建一个内部的watcher给计算属性用 vm, getter || noop, noop, computedWatcherOptions ); // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) //如果key在vm中没有定义 注:组件的计算属性在模块加载的时候已经被定义在了原型上面了 defineComputed(vm, key, userDef); //则执行defineComputed()函数 else 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);
注:对于计算属性的Watcher来说,它的lazy属性为true,因此new watcher()结尾时不会执行get()方法,而是直接返回undefined(在3127行)(求值会等到该计算属性被调用时才求值的)
回到initComputed()函数,defineComputed()定义如下:
function defineComputed ( //第3465行 target, key, userDef ) var shouldCache = !isServerRendering(); if (typeof userDef === ‘function‘) //如果userDef为函数,则默认为get
sharedPropertyDefinition.get = shouldCache //保存到get里
? createComputedGetter(key)
: userDef;
sharedPropertyDefinition.set = noop; //将set设为noop
else sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop; sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; if ("development" !== ‘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); //设置访问器属性,这样当我们在模板里访问计算属性时就会执行sharedPropertyDefinition的get方法了
初始化完成了,当我们的模板渲染成render函数时会执行如下函数
(function anonymous( //这是模板编译后生成的render函数 ) with(this)return _c(‘div‘,attrs:"id":"example",[_v(_s(reversedMessage))]) )
、获取reversedMessage属性时就会执行到计算属性的get访问器属性,也就是上面在3465行定义的defineComputed里的访问器属性,如下:
function createComputedGetter (key) //第3498行 return function computedGetter () var watcher = this._computedWatchers && this._computedWatchers[key]; //获取key对应的计算watcher if (watcher) if (watcher.dirty) //当watcher.dirty为true时 watcher.evaluate(); //执行watcher.evaluate()函数 if (Dep.target) //这个Dep.target存在(这是个渲染watcher) watcher.depend(); //则执行watcher.depend(); return watcher.value //最后返回计算属性的值
watcher函数对象的evaluate()和depend()对象都是为计算属性量身定制的,也就是说是它独有的,对于evaluate()来说,如下:
Watcher.prototype.evaluate = function evaluate() //为计算watcher量身定制的 this.value = this.get(); //调用计算属性的get方法,此时如果有依赖其他属性,则会在其他属性的dep对象里将当前计算watcher作为订阅者 this.dirty = false; //修正this.dirty为false,即一个渲染watcher渲染多个计算属性时,只会执行一次 ;
例子里执行完evaluate之后,Vue实例data里的message:‘hello world‘对应的subs就保存了当前的计算watcher,如下:
这样当message修改了之后就会触发计算watcher的更新了,回到createComputedGetter 里还会执行watcher.depend();,如下:
Watcher.prototype.depend = function depend () //第3254行 var this$1 = this; var i = this.deps.length; //获取计算watcher的所有deps while (i--) this$1.deps[i].depend(); //为该deps增加渲染watcher ;
执行完后会为当前计算属性所依赖的所有其它数据的订阅者subs里添加一个渲染watcher,执行完后Vue实例data里的message:‘hello world‘对应的subs又保存了当前的渲染watcher,如下:
现在计算watcher依赖的data属性对应的subs里存在两个订阅者了,当message进行修改时,会分别触发两个watcher的更新操作,reversedMessage也会进行相应的更新操作,然后DOM也更新了。
以上是关于Vue 2.0 深入源码分析 基础篇 computed 属性详解的主要内容,如果未能解决你的问题,请参考以下文章
vue 计算属性未重新计算 / computed 未触发 / computed 原理&源码分析