vue2数据绑定

数据绑定可以分为两部分

一、响应式数据的准备

注:下面的initState 调用的代码位于Vue.prototype._init中,而Vue.prototype._initinitMixin中执行时被定义,在vue.js文件中存在

image.png

这样几行代码,在你引入vue.js文件以后会执行initMixin等后续一系列方法,其中initMixin会为Vue这个构造函数挂载上_init这个方法,_initnew Vue时会被执行,之后会进入到initState方法中

主要位于生命周期的beforeCreated和created之间,其中最重要涉及的是initState函数

  function initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) { initProps(vm, opts.props); }
    if (opts.methods) { initMethods(vm, opts.methods); }
    if (opts.data) {
      initData(vm);
    } else {
      observe(vm._data = {}, true /* asRootData */);
    }
    if (opts.computed) { initComputed(vm, opts.computed); }
    if (opts.watch && opts.watch !== nativeWatch) {
      initWatch(vm, opts.watch);
    }
  }

这里涉及到了三个函数initData、initComputed、initWatch,其实这三个函数执行完毕后数据绑定的数据部分已经处理完毕了,下面展开看看

initData

关键代码就在于observe
这里先入为主一下,observe的目的就是给传进来的参数设置响应式,对于普通的对象和数组处理方式不同,普通对象因为存在对象嵌套的情况,所以会对key 进行循环,并为对应的value设置响应式,而数组会对下标循环,对value也设置响应式;其实整体和deepClone的逻辑十分类似

  function initData (vm) {
    var data = vm.$options.data;
    //这里省略了很多代码

    // observe data
    observe(data, true /* asRootData */);
  }

进入到observe,还是省去大量代码

  function observe (value, asRootData) {
    
    // 这里也省去了很多代码
    } else if (
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
    ) {
      ob = new Observer(value);
    }
  
  }

重点在于ob = new Observer(value);,那再看一下Observer的定义(省去了小部分代码)

  var Observer = function Observer (value) {
    this.dep = new Dep();
    if (Array.isArray(value)) {
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  };

这里并不能省去很多代码,但是精简过后看到主要的逻辑在于传进来的参数是否为数组
如果是数组,那么用observeArray就对数组进行特殊处理,如果是普通对象则使用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]);
    }
  };
//处理数组
  Observer.prototype.observeArray = function observeArray (items) {
    for (var i = 0, l = items.length; i < l; i++) {
      observe(items[i]);
    }
  };

那在处理数组中我们再一次看到了observe函数,到目前为止逻辑是这样的

image.png

那再看 defineReactive$$1defineReactive$$1为普通对象的每一个键值对利用defineProperty设置get和set方法构建响应式。在这里还是先精简一部分代码,理清流程

  function defineReactive$$1 (
      obj,
      key,
      val,
      customSetter,
      shallow
  ) {

    var childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        var value = getter ? getter.call(obj) : val
        return value
      },
      set: function reactiveSetter (newVal) {
        childOb = !shallow && observe(newVal);
      }
    });
  }

这样一来就清晰很多了。

强调:这里只是定义了get set的方法,并没有调用,当组件挂载的时候才会调用,后面会细讲,这里还是理清逻辑为主。

先看这一句

var childOb = !shallow && observe(val);

又出现了observe,这是用于解决对象嵌套的

{
  a: 1,
  b: {c: 2}
}

当循环到a:1时,observe(1)返回空,当循环到b: {c: 2}时,observe(b: {c: 2})返回observer实例,此时对于{c: 2}的响应式也构建完毕了。
总结下,上面的代码还未运行到Object.defineProperty时,已经对所有的子结构构建完响应式了
那么到目前为止的流程是这样的

image.png

其实到这里响应式基本就结束,下面结合mount时的代码说一下数据和视图时如何绑定的。

二、数据与视图之间的绑定

找到mountComponent函数,该函数会在$mount调用时被执行

  function mountComponent (
      vm,
      el,
      hydrating
  ) {
  //精简很多代码
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);

  }

核心代码new Watcher,看下watcher构造函数

  var Watcher = function Watcher (
      vm,
      expOrFn,
      cb,
      options,
      isRenderWatcher
  ) {
    this.vm = vm;
    if (options) {
      this.lazy = !!options.lazy;
      this.before = options.before;
    } else {
      this.deep = this.user = this.lazy = this.sync = false;
    }
    this.deps = [];
    this.value = this.lazy
        ? undefined
        : this.get();
  };

对比下形参和实参:
expOrFn=updateComponent
cb=noop
option= { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } }
得到执行时的相关变量:lazy=false
this.value的值为this.get()的返回值
getwatcher实例上的方法

  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 {
      if (this.deep) {
        traverse(value);
      }
      popTarget();
      this.cleanupDeps();
    }
    return value
  };

其中value = this.getter.call(vm, vm);中的getterupdateComponent这里就不多做介绍了,但是需要提到就是在updateComponent中执行的vm._render()会有对于data的访问,也就是触发defineProperty中的get方法。

重新回到defineReactive$$1中看看完整的代码

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
      },

出现了var dep = new Dep();

 var Dep = function Dep () {
    this.id = uid++;
    this.subs = [];
  };

其中subs 是用来保存依赖于当前数据的watcher的

强调:subs中存的是watcher实例

进入到get方法中
Dep.target表示的是当前处于执行状态的watcher,默认情况下为空

  Dep.target = null;
  var targetStack = [];

那么Dep.target什么时候会有值呢?再看一下watcher实例上的get方法

Watcher.prototype.get = function get () {
    pushTarget(this);
    try {
     //
    } finally {
     //
      popTarget();
    }
    return value
  };

这个两行很关键

  function pushTarget (target) {
    targetStack.push(target);
    Dep.target = target;
  }

  function popTarget () {
    targetStack.pop();
    Dep.target = targetStack[targetStack.length - 1];
  }

其中pushTargetDep.target设置为自己,设置完之后,访问了data中的数据,此时再看defineReactive$$1中的get

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
      },

先执行了dep.depend(),把当前激活的watcher添加到dep中

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

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

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

注意下调用链:dep调用depend,depend中把调用当前激活的watcher的addDep方法,参数为dep自身,addDep中dep把当前的激活的watcher添加到subs数组中

订阅发布模式:目前为止我们只做到了订阅,这里简单讲一下,vue为什么能做到精准的订阅。
我们再从头理一下。从watcher.get开始,watcher.get调用时会访问到视图中所需要的所有的data,同时watcher也会把自身设置为Dep.target,而访问data的过程中,每个data的key-value会创建一个dep对象,收集依赖这一个个data的watcher,恰好当前的watcher能访问到这些data,也因此对这些data产生了依赖,所以通过设置Dep.target能够准确收集依赖。但是做法不一定需要时Dep.target,只需要一个全局变量即可

同样的对于对象嵌套的情况,子对象的dep也应该把当前的watcher加入到数组中

if (childOb) 
     childOb.dep.depend();

再来看下set,相对而言简单了很多

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

主要就是这几行代码

set: function reactiveSetter (newVal) {
        var value = getter ? getter.call(obj) : val;
        /* eslint-disable no-self-compare */
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        childOb = !shallow && observe(newVal);
        dep.notify();
      }

对设置的新值进行observe,保证新值改变的时候也能被观测到
然后就是订阅发布的发布dep.notify(),主要就是对dep拷贝一份后执行subs中的watcher的回调函数,在这里就不展开了,具体的是利用promise将更新任务放入微队列中,然后取出dep中的watcher执行watcher.run,watcher.run中会执行wachter.get,也就是updatecomponent完成视图的更新

Dep.prototype.notify = function notify () {
    // stabilize the subscriber list first
    var subs = this.subs.slice();
    for (var i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
    }
  };
  Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  };

欢迎提问

你可能感兴趣的:(vue2数据绑定)