zoukankan      html  css  js  c++  java
  • Vue.js 源码分析(六) 基础篇 计算属性 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);
          }
        }
      }
    }

    writer by:大沙漠 QQ:22969969

    
    

    注:对于计算属性的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也更新了。

  • 相关阅读:
    上涨、下跌、震荡,我只做下跌 (有钱人赚钱太有心机!
    股票操作指南
    股票要素与心理学研究
    时序图组成
    软件描述的静态与动态
    用dedecms做网站时,空间服务器选择IIS还是apache???
    dedecms 图集标签{dede:productimagelist} {dede:field name='imgurls'}&nbs
    dede内容页调用图片集下所有图片方法!
    dede文章插入分页符不起作用,编辑器中出现分页符,导致文章显示不全
    dede织梦怎么修改description的字数
  • 原文地址:https://www.cnblogs.com/greatdesert/p/11044538.html
Copyright © 2011-2022 走看看