3

什么是计算属性?

计算属性就是根据一定的逻辑,将一个新属性与data数据的某个属性进行关联,由此获取与原数据对应的值。
以一个例子来说明:

<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。】

计算属性的作用就是让 Vue 知道 vm.newMessage 依赖于 vm.message,当 vm.message发生改变时,所有依赖 vm.newMessage 的绑定也会更新。
如图:

clipboard.png

用源码说话

initComputed

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)对计算属性进行初始化。

var watchers = vm._computedWatchers = Object.create(null);

initComputed ()接收vm,computed两个参数,
首先定义了一个空对象watchers,并赋值给_computedWatchers挂载到vm下。watchers就是以键值对的方式,存储计算属性对应的方法。

var isSSR = isServerRendering();

判断是不是服务器端渲染,计算属性在服务器渲染的情况下只有getter。如果是服务器端渲染,Vue不会为计算属性添加Watcher。
关于SSR的学习来自:https://codesky.me/archives/h...

  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);
      }
    }
  }
  1. 遍历用户定义的所有计算属性,创建变量userDef存储该计算属性的get方法。如果用户用下图newMessageForTest的方式定义计算属性,userDef存储的就是用户定义的get方法,如果是用下图newMessage的方式定义计算属性,Vue就把该计算属性对应的function作为它的getter。

clipboard.png

2. 为计算属性创建观察者Watcher,存放在上面定义的watchers空对象中。
3. 经过判断后调用defineComputed定义计算属性。

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...

createComputedGetter

function createComputedGetter (key) {
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      watcher.depend();
      return watcher.evaluate()
    }
  }
}
  1. 通过计算属性key的值找到对应的watcher,并启动该观察者进行观察。
  2. 通过watcher.depend()依赖收集绑定,本文就是将message和newMessag绑定。
    this.dep.depend();

watcher.depend()里通过this.dep(该依赖)调用depend()。

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(计算属性的关联属性)]}。

clipboard.png

上图是message的watcher,对应的expression就是Vue的_update()方法。

clipboard.png
这是newMessage的watcher,对应的expression就是用户自定义的get方法。

watcher.depend和evalute

当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
};

花花呀
375 声望23 粉丝

学无止境 做有灵魂的程序员