VUE源码分析之响应式系统介绍(一)

我们还是先看一个最简单的例子:

<!DOCTYPE html>  
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
</head>
<body>
    <div id="app">{{message}}</child></div>        
    <script>
       
        var app = new Vue({
            el:'#app',

            data:{

                    message:'Hello World'

            }
        })  
    </script>
</body>
</html>

例子非常简单,接触过VUE的一看便知,那么Hello World是怎么渲染到页面的呢? 我们一步一步分析这个过程,从而看看VUE的响应式原理。

在初始化Vue过程中调用了_init函数,走到了initState函数中,在这个函数中有对data属性的处理 initData。我们重点要看看这个函数:

  function initData (vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === 'function'
      ? getData(data, vm)
      : data || {};
    if (!isPlainObject(data)) {
      data = {};
      warn(
        'data functions should return an object:\n' +
        'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      );
    }
    // proxy data on instance
    var keys = Object.keys(data);
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    while (i--) {
      var key = keys[i];
      {
        if (methods && hasOwn(methods, key)) {
          warn(
            ("Method \"" + key + "\" has already been defined as a data property."),
            vm
          );
        }
      }
      if (props && hasOwn(props, key)) {
        warn(
          "The data property \"" + key + "\" is already declared as a prop. " +
          "Use prop default value instead.",
          vm
        );
      } else if (!isReserved(key)) {
        proxy(vm, "_data", key);
      }
    }
    // observe data
    observe(data, true /* asRootData */);
  }

通过打印我们可以得到data为{"message": "Hello World"}。在while循环中走到了proxy(vm, "_data", key) 里面,这个函数挺关键的,调用完这个函数就给vm创建了message属性。接着我们observe  data。在observe函数中实例化了一个Observer对象。

我们重点看看这个Observer构造函数:

  var Observer = function Observer (value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  };

在这个构造函数中实例化了一个Dep对象,这个对象就是依赖收集器。先不看Dep对象的构造函数了,接着往下走看看这个Observer对象的walk方法:

  Observer.prototype.walk = function walk (obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
      defineReactive$$1(obj, keys[i]);
    }
  };

遍历data所有的属性,调用defineReactive$$1来使data变成响应式对象。看看具体这个过程:

  function defineReactive$$1 (
    obj,
    key,
    val,
    customSetter,
    shallow
  ) {
    var dep = new Dep();

    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) {
      return
    }

    // cater for pre-defined getter/setters
    var getter = property && property.get;
    var setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
      val = obj[key];
    }

    var childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        var value = getter ? getter.call(obj) : val;
        if (Dep.target) {
          dep.depend();
          if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
              dependArray(value);
            }
          }
        }
        return value
      },
      set: function reactiveSetter (newVal) {
        var value = getter ? getter.call(obj) : val;
        /* eslint-disable no-self-compare */
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        /* eslint-enable no-self-compare */
        if (customSetter) {
          customSetter();
        }
        // #7981: for accessor properties without setter
        if (getter && !setter) { return }
        if (setter) {
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
        childOb = !shallow && observe(newVal);
        dep.notify();
      }
    });
  }

    用到了Object.defineProperty   这个过程就不多说了。看看我们设置的get 函数 和 set 函数。首先我们实例化了一个Dep对象,

在get函数中我们调用了这个Dep对象的depend方法,在set函数中我们调用了Dep对象的notify方法。get 函数 set 函数调用Dep对象了,这个是闭包原理的范畴,那么这个实例化的Dep对象会一直存在。从上面的分析我们看出我们给Data 里面的每个属性都添加了get set函数,并且都有一个Dep对象实例。 什么时候会调用到get函数呢?

     在对模板编译后我们生产了render 函数,通过打印我们看看这个render函数:

 function anonymous(){

     with(this){return _c('div', {attrs:{"id":"app"}}, [_v("\n   "+_s(message)+"\n")])}

}

在创建VNode过程中调用:vnode = render.call(vm._renderProxy, vm.$createElement);  通过前面几篇文章知道render就是上面的函数。关于with这个关键字的用法就不多说了,可自行百度一下。我们也知道在进行调用函数时先进行参数的传递,如果参数是个表达式,当然要先进行表达式的计算。

     我们在initData函数中调用的proxy函数给vm添加了message属性,这里就用到了,vm[_data][message] 来取得“Hello World"

而我们observe 了data对象,所以会调用我们对message属性设置的get 函数。调用get函数的时机找到了。我们接着看看get函数里面的内容,这个函数里面重点在这几行代码:

        if (Dep.target) {
          dep.depend();
          if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
              dependArray(value);
            }
          }
        }

 通过打印发现if条件成立,进入了dep.depend函数。我们先不看if条件成立这个地方,先看看dep.depend做了什么。

  Dep.prototype.depend = function depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  };

代码看着很简单,if条件成立的话,调用addDep函数。看来if条件成立这个地方还是绕不过了。不太好找,直接上答案了。

    if (config.performance && mark) {
      updateComponent = function () {
        var name = vm._name;
        var id = vm._uid;
        var startTag = "vue-perf-start:" + id;
        var endTag = "vue-perf-end:" + id;

        mark(startTag);
        var vnode = vm._render();
        mark(endTag);
        measure(("vue " + name + " render"), startTag, endTag);

        mark(startTag);
        vm._update(vnode, hydrating);
        mark(endTag);
        measure(("vue " + name + " patch"), startTag, endTag);
      };
    } else {
      updateComponent = function () {
        vm._update(vm._render(), hydrating);
      };
    }

    // we set this to vm._watcher inside the watcher's constructor
    // since the watcher's initial patch may call $forceUpdate (e.g. inside child
    // component's mounted hook), which relies on vm._watcher being already defined
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);

我们在创建VNode时调用了mountComponent函数,在这个函数里面有上面的代码片段。我们实例化了一个Watcher对象,且传给这个Watcher对象最后一个参数 isRenderWathcer 是true,从注释我们就看到这是个render Watcher,回调函数是updateComponent, 回调函数完成了创建VNone的过程,以及生产真正Dom的过程。现在我们看看这个Watcher对象的构造函数:

  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();
  };

一系列赋值操作,看看最后几行:

this.value = this.lazy
      ? undefined
      : this.get();

我们并没有传递lazy参数,所以this.lazy为false,我们要调用this.get() ,也就是这个Watcher对象的get 方法:

  Watcher.prototype.get = function get () {
    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();
      this.cleanupDeps();
    }
    return value
  };

在这个函数里面我们发生了pushTarget 和 popTarget 函数,还有this.getter.call(vm, vm)。 也就是说先把render Watcher 推入target当中,做完创建VNode, 创建真实Dom后再把render Watcher推出target。所以我们在get 函数中的if 条件就成立了。

而且调用了render Watcher中的addDep方法。看看这个addDep方法:

  Watcher.prototype.addDep = function addDep (dep) {
    var id = dep.id;
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  };

这个方法把message属性对应的dep对象推入了render Watcher 对象的newDeps当中,并且调用了dep对象的addSub 方法,这个方法把render Watcher 推入了dep对象中的subs中。看看dep对象的adaSub方法:

  Dep.prototype.addSub = function addSub (sub) {
    this.subs.push(sub);
  };

确实是这样的。

通过上面的分析我们可以看出,data中的message属性创建了一个依赖收集器dep, 如果我们页面依赖message属性,依赖收集器就把render Watcher加入到自己subs里面,而render Watcher 也把依赖收集器加入到自己的deps里面。整个响应式系统也就建立了。

如果改变message值,页面是否也随之更新呢?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值