vue

vue解析2-响应式数据

2020-04-06  本文已影响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
  });
});
上一篇 下一篇

猜你喜欢

热点阅读