计算属性就是根据必定的逻辑,将一个新属性与data数据的某个属性进行关联,由此获取与原数据对应的值。
以一个例子来讲明:javascript
<div id="test"> 你输入的:<input type="text" v-model="message"><br/> 将变成:<input type="text" v-model="newMessage" disabled> </div>
let vm = new Vue({ el:'#test', data:{ message:''}, computed:{ newMessage:function(){ return this.message==''?'':this.message+',哈哈!'; }, newMessageForTest:{ get:function(){ return this.message==''?'':this.message+',嘿嘿!'; } } } });
【这里提供了写两种计算属性的方法。newMessage默认对应的方法为message的getter方法,而newMessageForTest为message专门提供了get。】html
计算属性的做用就是让 Vue 知道 vm.newMessage 依赖于 vm.message,当 vm.message发生改变时,全部依赖 vm.newMessage 的绑定也会更新。
如图:vue
function initState (vm) { 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); } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
Vue在初始化组件的时候调用initState(),此时若用户配置了computed,则进入initComputed(vm, opts.computed)对计算属性进行初始化。java
var watchers = vm._computedWatchers = Object.create(null);
initComputed ()接收vm,computed两个参数,
首先定义了一个空对象watchers,并赋值给_computedWatchers挂载到vm下。watchers就是以键值对的方式,存储计算属性对应的方法。express
var isSSR = isServerRendering();
判断是否是服务器端渲染,计算属性在服务器渲染的状况下只有getter。若是是服务器端渲染,Vue不会为计算属性添加Watcher。
关于SSR的学习来自:https://codesky.me/archives/h...segmentfault
for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get; if ("development" !== '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 (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); } } }
2. 为计算属性建立观察者Watcher,存放在上面定义的watchers空对象中。 3. 通过判断后调用defineComputed定义计算属性。
if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef; sharedPropertyDefinition.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);
defineComputed 接收了target,key,userDef三个参数,分别是该组件,计算属性,以及计算属性对应的方法,这段代码根据组件的不一样状态,将计算属性绑定到组件上。
关于Object.defineProperty学习于https://segmentfault.com/a/11...服务器
function createComputedGetter (key) { return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { watcher.depend(); return watcher.evaluate() } } }
this.dep.depend();
watcher.depend()里经过this.dep(该依赖)调用depend()。oop
Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); } };
而Dep的depend方法调用了Watcher的addDep方法。学习
Watcher.prototype.addDep = function addDep (dep) { var id = dep.id; if (!this.newDepIds.has(id)) { this.newDepIds.add(id); this.newDeps.push(dep); if (!this.depIds.has(id)) { dep.addSub(this); } } };
addDep将依赖dep push到newDeps中,将dep的id push到newDepIds。dep{id:2,subs:[watcher(计算属性),watcher2(计算属性的关联属性)]}。this
上图是message的watcher,对应的expression就是Vue的_update()方法。
这是newMessage的watcher,对应的expression就是用户自定义的get方法。
当message发生改变时,触发对应的dep.notify()方法,发布通知观察者watcher执行update,以后会触发watcher的getter方法获取计算属性改变后关联属性的值,并将新的值更改到watcher对象下,进行数据更新了。
(Watcher.prototype.get):
value = this.getter.call(vm, vm);
(Watcher.prototype.getAndInvoke):
// set new value var oldValue = this.value; this.value = value; this.dirty = false;
watcher.evalute()就是返回挂载到watcher下的新的value。
Watcher.prototype.evaluate = function evaluate () { if (this.dirty) { this.value = this.get(); this.dirty = false; } return this.value };