vue源码分析(二)>>:watch

今天来分析一下watch源码实现方式,watch就是实现某个属性发生变化立即得到通知。

step1:watch用法:

  基本用法如下(写的比较粗糙,看不明白的看这篇基础点的:https://www.cnblogs.com/rainbowLover/p/13036284.html):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue-watch</title>
</head>
<body>
  <div id="app">
    <p>info.name-{{info.name}}</p>
    <p>info.age-{{info.age}}</p>
    <p>hoppy-{{hobby}}</p>
    <p>tip-{{tip}}</p>
    <button @click="handleClick">click</button>
  </div>

  <script src="../../dist/vue.min.js"></script>
  <script>
    let vm = new Vue({
      el:'#app',
      data:{
        info:{
          name:'张三',
          age:22
        },
        hobby:'love',
        tip:'哈哈'
      },
      mounted() {
        // 用Vue的$watch添加,参数一:监听的属性名,参数二:回调,参数三:配置
        this.$watch('tip',this.tipChanged,{immediate:true})
      },
      methods: {
        handleClick(){
          this.info.name = '李四'
          this.info.age = 66
          this.hobby = 'make'
          this.tip = '呵呵'
        },
        tipChanged(){
          console.log("tip watch >>:", arguments);
        }
      },
      watch: {
        hobby(){// 方法名就是监听的属性名
          console.log("hobby watch >>:", arguments);
        },
        'info.name':function(){// 监听info的属性name
          console.log("info.name watch >>:", arguments);
        },
        info:{// 监听info
          immediate:true,// 立马执行 页面加载时候会执行一次回调方法
          deep:true,// 开启深度监听的话 info的属性变化也会触发回调,
          handler:function(){// 回调
            console.log("info watch >>:", arguments);
          }
        }
      }
    })
  </script>
</body>
</html>

 

 

step2:watch源码实现:

  vue在实例化时候走_init()方法,在beforeCreate和created之间回调用initState方法,继续追踪这个方法:这个方法是初始化数据相关的对象,在这个方法里边看到先后执行了:initProps、initMethods、initData、initComputed,最后执行initWatch方法。看看这个方法:

  function initWatch (vm, watch) {
    for (var key in watch) {
      var handler = watch[key];
      if (Array.isArray(handler)) {
        for (var i = 0; i < handler.length; i++) {
          createWatcher(vm, key, handler[i]);
        }
      } else {
        createWatcher(vm, key, handler);
      }
    }
  }

断点看到watch是:就是界面上定义的watch里边的东西

这里看到watch[key]还可以是数组?然后我赶紧去试一下:改成这样

发现hobby变化时候这两个方法都执行了!然后继续往下看:createWatcher

// 查找发现 这个方法只被初始化watch过程调用
  // 参数一:vue实例对象
  // 参数二:key
  // 参数三:定义的方法或者深度监听等配置
  // 参数四:深度监听等配置 只有this.$watch()方法定义watch时候使用,
  function createWatcher (
    vm,
    expOrFn,
    handler,
    options
  ) {
    // 如果是对象 就是深度监听那种定义方法 info的那个定义
    if (isPlainObject(handler)) {
      options = handler;
      handler = handler.handler;
    }
    // 如果是字符串 就去看看方法里边定义的该方法
    if (typeof handler === 'string') {
      handler = vm[handler];
    }
    // 调用$watch
    return vm.$watch(expOrFn, handler, options)
  }

通过这个方法,把不同方式定义的watch规范一下,找到回调方法,

看到handler还能是字符串,我试一下:可以触发

然后继续看$watch方法:

// watch操作 
    Vue.prototype.$watch = function (
      expOrFn,
      cb,
      options
    ) {
      debugger
      var vm = this;
      // 如果handler还不是方法 还是对象  递归调用createWatcher
      if (isPlainObject(cb)) {
        return createWatcher(vm, expOrFn, cb, options)
      }
      options = options || {};
      options.user = true;// 标注这些watch都是用户整的
      var watcher = new Watcher(vm, expOrFn, cb, options);// 创建一个Watcher实例
      if (options.immediate) {// 如果有immediate属性 就立即执行一下子
        try {
          cb.call(vm, watcher.value);
        } catch (error) {
          handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
        }
      }
      return function unwatchFn () {// 设置完  返回一个注销监听的方法
        watcher.teardown();
      }
    };

在这里可以看到传immediate有撒用了,继续看实例化Watcher都有哪些操作:

/**
   * A watcher parses an expression, collects dependencies,
   * and fires callback when the expression value changes.
   * This is used for both the $watch() api and directives.
   */
  var Watcher = function Watcher (
    vm,
    expOrFn,
    cb,
    options,
    isRenderWatcher
  ) {
    this.vm = vm;
    if (isRenderWatcher) {
      vm._watcher = this;
    }
    vm._watchers.push(this);
    // options
    if (options) {
      this.deep = !!options.deep;
      this.user = !!options.user;
      this.lazy = !!options.lazy;
      this.sync = !!options.sync;
      this.before = options.before;
    } else {
      this.deep = this.user = this.lazy = this.sync = false;
    }
    this.cb = cb;
    this.id = ++uid$2; // uid for batching
    this.active = true;
    this.dirty = this.lazy; // for lazy watchers
    this.deps = [];
    this.newDeps = [];
    this.depIds = new _Set();
    this.newDepIds = new _Set();
    this.expression = expOrFn.toString();
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      this.getter = parsePath(expOrFn);
      if (!this.getter) {
        this.getter = noop;
        warn(
          "Failed watching path: \"" + expOrFn + "\" " +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        );
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get();
  };

  /**
   * Parse simple path.
   */
  var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]"));
  function parsePath (path) {
    if (bailRE.test(path)) {
      return
    }
    var segments = path.split('.');
    return function (obj) {
      for (var i = 0; i < segments.length; i++) {
        if (!obj) { return }
        obj = obj[segments[i]];
      }
      return obj
    }
  }

然后看get方法:

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  Watcher.prototype.get = function get () {
    debugger
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
      value = this.getter.call(vm, vm);
    } 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
  };

看看如果是深度监听 , 他会怎么操作递归给每个子属性都收集依赖:

var seenObjects = new _Set();

  /**
   * Recursively traverse an object to evoke all converted
   * getters, so that every nested property inside the object
   * is collected as a "deep" dependency.
   */
  function traverse (val) {
    _traverse(val, seenObjects);
    seenObjects.clear();
  }

  function _traverse (val, seen) {
    var i, keys;
    var isA = Array.isArray(val);
    if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
      return
    }
    if (val.__ob__) {
      var depId = val.__ob__.dep.id;
      if (seen.has(depId)) {
        return
      }
      seen.add(depId);
    }
    if (isA) {
      i = val.length;
      while (i--) { _traverse(val[i], seen); }
    } else {
      keys = Object.keys(val);
      i = keys.length;
      // 递归获取子值,触发getter,收集依赖,此时Watcher不为空
      while (i--) { _traverse(val[keys[i]], seen); }
    }
  }

 

step3:watch触发:

当我们修改属性值时候,就会触发该属性的set方法,就会执行watcher的update方法,然后会触发Watcher的run方法:然后回调咱们写的回调:

 /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  Watcher.prototype.update = function update () {
    console.log("Watcher update");
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  };

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  Watcher.prototype.run = function run () {
    console.log("Watcher run");
    if (this.active) {
      var value = this.get();
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        var oldValue = this.value;
        this.value = value;
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue);// 回调执行
          } catch (e) {
            handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
          }
        } else {
          this.cb.call(this.vm, value, oldValue);
        }
      }
    }
  };

 总结:收获什么了? 什么也没收获?什么也没收获吗?

 over!

posted on 2020-08-27 18:05  rainbowLover  阅读(239)  评论(0编辑  收藏  举报