美文网首页vue
vue解析2-响应式数据

vue解析2-响应式数据

作者: 百里哈哈 | 来源:发表于2020-04-06 21:09 被阅读0次

    简单的示例

    在实际的vue应用中watcher对象与vm实例是分不开的,然而为了简化observe、 dep、watcher的分析过程,特地采用了一个脱离vm的示例进行演示。

    import Vue from 'vue'
    
    function test1() {
        let observable = Vue.observable;
        let Watcher = Vue.Watcher;
        let person = {
            name: 'haha',
            age: 18
        }
        observable(person)
        person._watchers = [];
        let watcher = new Watcher(person, 'age', function (val) {
            console.log('the age is' , val)
        })
        console.log(watcher.value)
        person.age = 16;
        // person.age = 17;
        Vue.nextTick(function () {
            console.log(watcher.value)
        })
    }
    
    test1();
    
    

    该例子中在Vue上额外挂载了watcher对象,以便对外暴露。
    1.通过observable将data数据进行get、set的设置
    2.通过new Watcher 实现对age数据的监听
    3.watcher.update会推入到一个异步队列进行批次更新, 所以在 Vue.nextTick可取到最新的值

    observe、dep、watcher关系图

    observe.png

    1.observer递归遍历data数据,为其属性设置get/set

    1. 在observer的get方法中实现依赖收集, 对使用到该数据的watcher对象进行订阅。
      3.在observer的set方法中,数据发生变更则通知其订阅的watcher对象进行update
      4.在new Watcher的时候会对其监听的数据进行一次获取即this.getter方法, 通过该方法会进入到observer的get方法中
      5.dep.notify通知其订阅的watcher进行更新, 通常情况下会将更新的对象放入一个queue队列采用异步更新。

    watcher部分源码

    var Watcher = function Watcher (
      vm,
      expOrFn,
      cb,
      options,
      isRenderWatcher
    ) {
      this.vm = vm;
      if (isRenderWatcher) {
        vm._watcher = this;
      }
      vm._watchers.push(this);
      // options
     ...
      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 = process.env.NODE_ENV !== 'production'
        ? expOrFn.toString()
        : '';
      // parse expression for getter
      if (typeof expOrFn === 'function') {
        this.getter = expOrFn;
      } else {
        this.getter = parsePath(expOrFn);
        if (!this.getter) {
          this.getter = noop;
          process.env.NODE_ENV !== 'production' && 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();
    };
    

    $get & run

    Watcher.prototype.get = function get () {
      pushTarget(this);
      var value;
      var vm = this.vm;
      try {
        value = this.getter.call(vm, vm);
      } catch (e) {
        ...
      } finally {
        // "touch" every property so they are all tracked as
        // dependencies for deep watching
        if (this.deep) {
          traverse(value);
        }
        popTarget();
        this.cleanupDeps();
      }
      return value
    };
    
    Watcher.prototype.run = function run () {
      if (this.active) {
        var value = this.get();
        ...
        }
      }
    };
    

    render Watcher

    new Watcher.png

    1.每个vm实例都会进行一个new watcher, 在该对象的getter上挂载updateComponent方法

    1. 在get方法中调用updateComponent完成render的vnode以及真实节点的渲染, 在render的过程中template中涉及的渲染数据都会对该watcher对象进行订阅

    watcher.cleanupDeps作用

    在写Vue模板的时候,模板中涉及的渲染数据都会对render watcher进行订阅。 如果我们更改的数据并不会影响到页面的渲染,安装正常的逻辑是不需要对render watcher进行订阅的。 例如v-if的一些条件判断,在false的情况 下其中的data不需要订阅render watcher, 所以需要对其进行解绑。测试用例如下

    <template>
        <div>
            <p v-if="countFlag"> you counter is {{count}}</p>
            <p>another is {{msg}}</p>
            <span @click="countInCreate">to create num</span>
            <span @click="setCountFlag(false)">hide counter</span>
        </div>
    </template>
    <script>
    export default {
        data () {
            return {
                count: 1,
                msg: 'test if',
                countFlag: true
            }
        },
        methods: {
            countInCreate() {
                this.count++;
            },
            setCountFlag(val) {
                this.countFlag = false;
            }
        }
    }
    </script>
    

    可在Watcher.prototype.get中打断点进行测试, 点击countInCreate方法, 断点可以打入get中,接下来如果将this.countFlag设为false,则watcher.run并不会执行。

    array方法重写

    在Vue的开发过程中经常会遇到一些关于数组没有自动监听响应的问题, Vue只是对array的一些原生方法进行了重写。有下面代码可知使用push、pop、shift、unshift、splice、sort、reverse可实现自动监听, 爱用concat的小伙伴需谨慎。

    var arrayProto = Array.prototype;
    var arrayMethods = Object.create(arrayProto);
    
    var methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ];
    
    /**
     * Intercept mutating methods and emit events
     */
    methodsToPatch.forEach(function (method) {
      // cache original method
      var original = arrayProto[method];
      def(arrayMethods, method, function mutator () {
        var args = [], len = arguments.length;
        while ( len-- ) args[ len ] = arguments[ len ];
    
        var result = original.apply(this, args);
        var ob = this.__ob__;
        var inserted;
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args;
            break
          case 'splice':
            inserted = args.slice(2);
            break
        }
        if (inserted) { ob.observeArray(inserted); }
        // notify change
        ob.dep.notify();
        return result
      });
    });
    

    相关文章

      网友评论

        本文标题:vue解析2-响应式数据

        本文链接:https://www.haomeiwen.com/subject/khebphtx.html