vue源码分析(三)>>:computed

看完watch在看看computed时怎么实现的:

step1:用法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>computed</title>
</head>
<body>
  <div id="app">
    <p>欢迎:{{userName}}</p>
    <p>欢迎:{{userNameM()}}</p>
    <button @click="handleClick">click</button>
  </div>

  <script src="../../dist/vue.js"></script>
  <script>
    let vm = new Vue({
      el:'#app',
      data:{
        user:{
          firstName:'',
          lastName:'三丰'
        },
      },
      methods: {
        handleClick(){
          this.user.firstName = "";
        },
        userNameM(){
          return this.user.firstName + this.user.lastName;
        }
      },
      computed: {
        userName(){
          return this.user.firstName + this.user.lastName;
        }
      },
    })
  </script>
</body>
</html>

可以把计算属性看成一般的属性用,watch里边也能监听到userName的变化;另外用方法也能实现同样的效果,先写上。

 

step2:源码实现

还是从_init开始看起,到initState,在这里在initWatch之前,走initComputed方法,传参第一个参数是:vm,第二个参数是:vm.$options.computed;第一个参数就是当前示例对象,第二个参数就是我们所写的computed对象,看这个方法:

// 初始化computed
  function initComputed (vm, computed) {
    debugger
    // $flow-disable-line
    var watchers = vm._computedWatchers = Object.create(null);
    // computed properties are just getters during SSR
    var isSSR = isServerRendering();// 看看是不是服务端渲染

    for (var key in computed) {
      var userDef = computed[key];
      var getter = typeof userDef === 'function' ? userDef : userDef.get;
      if (getter == null) {
        warn(
          ("Getter is missing for computed property \"" + key + "\"."),
          vm
        );
      }

      if (!isSSR) {
        // create internal watcher for the computed property. 
        // 定义watcher
        watchers[key] = new 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和其它方法或者属性冲突就抛异常
        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);
        }
      }
    }
  }

然后创建Watcher,这和watch时候的一样了就,下边走defineComputed:就是创建它的getter

  function defineComputed (
    target,
    key,
    userDef
  ) {
    var shouldCache = !isServerRendering();
    if (typeof userDef === 'function') {
      sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)// 走这里
        : createGetterInvoker(userDef);
      sharedPropertyDefinition.set = noop;
    } else {
      sharedPropertyDefinition.get = userDef.get
        ? shouldCache && userDef.cache !== false
          ? createComputedGetter(key)
          : createGetterInvoker(userDef.get)
        : noop;
      sharedPropertyDefinition.set = userDef.set || noop;
    }
    if (sharedPropertyDefinition.set === noop) {
      sharedPropertyDefinition.set = function () {
        warn(
          ("Computed property \"" + key + "\" was assigned to but it has no setter."),
          this
        );
      };
    }
    Object.defineProperty(target, key, sharedPropertyDefinition);
  }

  function createComputedGetter (key) {
    debugger
    return function computedGetter () {
      var watcher = this._computedWatchers && this._computedWatchers[key];
      if (watcher) {
        if (watcher.dirty) {
          watcher.evaluate();
        }
        if (Dep.target) {
          watcher.depend();
        }
        return watcher.value
      }
    }
  }

  function createGetterInvoker(fn) {
    return function computedGetter () {
      return fn.call(this, this)
    }
  }

至此:初始化工作就完成了。

 

step3:触发逻辑

当我们获取userName时候就会触发他的get方法就是上面 computedGetter方法,debugger已经打上了,debug走一下:

  function createComputedGetter (key) {
    return function computedGetter () {
      debugger
      var watcher = this._computedWatchers && this._computedWatchers[key];// 获取watcher对象
      if (watcher) {
        if (watcher.dirty) {// 如果dirty为true
          watcher.evaluate();//获取数据
        }// 否则就直接返回
        if (Dep.target) {
          watcher.depend();
        }
        return watcher.value
      }
    }
  }

断点看到,界面上渲染时候获取也就是第一次获取,这个dirty是true就去获取了,我第二次获取时候,dirty是false直接返回的watcher.value,接下来看看watcher.evaluate()方法:

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  Watcher.prototype.evaluate = function evaluate () {
    console.log("Watcher evaluate");
    this.value = this.get();// 获取值 赋值给当前watcher
    this.dirty = false;// 把当前dirty设为false
  };

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  Watcher.prototype.get = function get () {
    debugger
    console.log("Watcher get");
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
      value = this.getter.call(vm, vm);// 走getter方法获取值
    } catch (e) {
      if (this.user) {
        handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {// 如果是深度监听
        traverse(value);
      }
      popTarget(); // 对应 pushTarget(this);
      this.cleanupDeps();
    }
    return value
  };

现在分析下这个dirty,当user里边的firstName或lastName改变时候会触发watcher的update方法,会改变这个dirty为true,在随后获取时候就不会直接返回wather.value了;这里就体现出和用方法实现起来的区别了,区别就是这个computed会缓存,这个dirty逻辑就是缓存。

 

暂时就这样

 

over!

 

posted on 2020-08-27 22:01  rainbowLover  阅读(201)  评论(0编辑  收藏  举报