欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue 2.0 深入源码分析(六) 基础篇 computed 属性详解

程序员文章站 2022-03-22 12:32:33
用法 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护,比如: 这样模板不再是简单的声明式逻辑,必须看一段时间才能意识到,对于这些复杂逻辑,需要使用计算属性,例如: 在模板中可以把computed当作data属性来使用 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,如下:

Vue 2.0 深入源码分析(六) 基础篇 computed 属性详解

这样当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,如下:

 Vue 2.0 深入源码分析(六) 基础篇 computed 属性详解

现在计算watcher依赖的data属性对应的subs里存在两个订阅者了,当message进行修改时,会分别触发两个watcher的更新操作,reversedmessage也会进行相应的更新操作,然后dom也更新了。