zoukankan      html  css  js  c++  java
  • 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!

  • 相关阅读:
    四川省选2012 day1 喵星球上的点名 (后缀数组,并不是完全的正解)
    6.2.1 最短路
    5.3.3 敌兵布阵
    6.1.1 Constructing Roads
    6.2.4 Arbitrage
    6.1.6 Jungle Roads
    5.3.6 Cow Sorting (HDU 及 POJ)
    6.2.5 Trucking
    6.1.4 还是畅通工程
    6.1.3 畅通工程
  • 原文地址:https://www.cnblogs.com/rainbowLover/p/13572949.html
Copyright © 2011-2022 走看看