Vue源码解读之Dep,Observer和Watcher

在解读Dep,Observer和Watcher之前,首先我去了解了一下Vue的数据双向绑定,即MVVM,学习于:https://blog.csdn.net/u013321...
以及关于Observer和watcher的学习来自于:https://www.jb51.net/article/...

整体过程

Vue实例化一个对象的具体过程如下:

  1. 新创建一个实例后,Vue调用compile将el转换成vnode。
  2. 调用initState, 创建props, data的钩子以及其对象成员的Observer(添加getter和setter)。
  3. 执行mount挂载操作,在挂载时建立一个直接对应render的Watcher,并且编译模板生成render函数,执行vm._update来更新DOM。
  4. 每当有数据改变,都将通知相应的Watcher执行回调函数,更新视图。

    • 当给这个对象的某个属性赋值时,就会触发set方法。
    • set函数调用,触发Dep的notify()向对应的Watcher通知变化。
    • Watcher调用update方法。

在这里插入图片描述

在这个过程中:

  1. Observer是用来给数据添加Dep依赖。
  2. Dep是data每个对象包括子对象都拥有一个该对象, 当所绑定的数据有变更时, 通过dep.notify()通知Watcher。
  3. Compile是HTML指令解析器,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
  4. Watcher是连接Observer和Compile的桥梁,Compile解析指令时会创建一个对应的Watcher并绑定update方法 , 添加到Dep对象上。

在这里插入图片描述

接下来我们来分析一下对象具体的代码实现。

Observer


var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  // 给value添加__ob__属性,值就是本Observer对象,value.__ob__ = this;
  // Vue.$data 中每个对象都 __ob__ 属性,包括 Vue.$data对象本身
  def(value, '__ob__', this);
  //判断是否为数组,不是的话调用walk()添加getter和setter
  //如果是数组,调用observeArray()遍历数组,为数组内每个对象添加getter和setter
  if (Array.isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment;
    augment(value, arrayMethods, arrayKeys);
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};

walk和defineReactive


// 遍历每个属性并将它们转换为getter/setter。只有当值类型为对象时才调用此方法。
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i]);
  }
};

function defineReactive (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();
  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }
  //获取已经实现的 getter /setter 方法
  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;
      //Dep.target 全局变量指向的就是当前正在解析指令的Complie生成的 Watcher
      // 会执行到 dep.addSub(Dep.target), 将 Watcher 添加到 Dep 对象的 Watcher 列表中
      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 ("development" !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();//如果数据被重新赋值了, 调用 Dep 的 notify 方法, 通知所有的 Watcher
    }
  });
}

observeArray和observe


Observer.prototype.observeArray = function observeArray (items) {
  for (var i = 0, l = items.length; i < l; i++) {
    // 如果是数组继续执行 observe 方法, 其中会继续新建 Observer 对象, 直到穷举完毕执行 walk 方法
    observe(items[i]);
  }
};

function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

Dep


// Dep是订阅者Watcher对应的数据依赖
var Dep = function Dep () {
  //每个Dep都有唯一的ID
  this.id = uid++;
  //subs用于存放依赖
  this.subs = [];
};

//向subs数组添加依赖
Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};
//移除依赖
Dep.prototype.removeSub = function removeSub (sub) {
  remove(this.subs, sub);
};
//设置某个Watcher的依赖
//这里添加了Dep.target是否存在的判断,目的是判断是不是Watcher的构造函数调用
//也就是说判断他是Watcher的this.get调用的,而不是普通调用
Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

Dep.prototype.notify = function notify () {
  var subs = this.subs.slice();
  //通知所有绑定 Watcher。调用watcher的update()
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

Watcher

在initMixin()初始化完成Vue实例所有的配置之后,在最后根据el是否存在,调用$mount()实现挂载。


if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }

$mount


//这是供外部使用的公共的方法
Vue.prototype.$mount = function (
  el,
  hydrating
) {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating)
};

mountComponent


function mountComponent (
  vm,
  el,
  hydrating
) {
  vm.$el = el;
  //这个if判断目的在检测vm.$options.render
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode;
    {
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        );
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        );
      }
    }
  }
  // 调用钩子函数
  callHook(vm, 'beforeMount');

  // 定义updateComponent,将作为Watcher对象的参数传入。
  var updateComponent;
  /* istanbul ignore if */
  if ("development" !== 'production' && 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);
    };
  }


  new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);
  hydrating = false;

  // 调用钩子
  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
  return vm
}

watcher

mountComponent在构造新的Watcher对象传了当前vue实例、updateComponent函数、空函数这三个参数。


var Watcher = function Watcher (
  vm,
  expOrFn,
  cb,
  options,
  isRenderWatcher
) {
  this.vm = vm;
  if (isRenderWatcher) {
    vm._watcher = this;
  }
  // 当前Watcher添加到vue实例上
  vm._watchers.push(this);
  // 参数配置,options默认false
  if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.computed = !!options.computed;
    this.sync = !!options.sync;
    this.before = options.before;
  } else {
    this.deep = this.user = this.computed = this.sync = false;
  }
  this.cb = cb;
  this.id = ++uid$1; // uid for batching
  this.active = true;
  this.dirty = this.computed; //用于计算属性
  this.deps = [];
  this.newDeps = [];
  //内容不可重复的数组对象
  this.depIds = new _Set();
  this.newDepIds = new _Set();
  this.expression = expOrFn.toString();
  //将watcher对象的getter设为updateComponent方法
  if (typeof expOrFn === 'function') {
    this.getter = expOrFn;
  } else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = function () {};
      "development" !== 'production' && warn(
        "Failed watching path: \"" + expOrFn + "\" " +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      );
    }
  }
  //如果是计算属性,就创建Dep数据依赖,否则通过get获取value
  if (this.computed) {
    this.value = undefined;
    this.dep = new Dep();
  } else {
    this.value = this.get();
  }
};
};

get


Watcher.prototype.get = function get () {
  pushTarget(this);//将Dep的target添加到targetStack,同时Dep的target赋值为当前watcher对象
  var value;
  var vm = this.vm;
  try {
   // 调用updateComponent方法
    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();//update执行完成后,又将Dep.target从targetStack弹出。
    this.cleanupDeps();
  }
  return value
};

//这是全局唯一的,因为任何时候都可能只有一个watcher正在评估。
Dep.target = null;
var targetStack = [];

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

function popTarget () {
  Dep.target = targetStack.pop();
}

Watcher的get方法实际上就是调用了updateComponent方法,updateComponent就是


    updateComponent = function() {
        vm._update(vm._render(), hydrating);
    };

调用这个函数会接着调用_update函数更新dom,这个是挂载到vue原型的方法,而_render方法重新渲染了vnode。


Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;
    // reset _rendered flag on slots for duplicate slot check
    {
      for (var key in vm.$slots) {
        // $flow-disable-line
        vm.$slots[key]._rendered = false;
      }
    }
    if (_parentVnode) {
      vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject;
    }
    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode;
    // render self
    var vnode;
    try {
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
      handleError(e, vm, "render");
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      {
        if (vm.$options.renderError) {
          try {
            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
          } catch (e) {
            handleError(e, vm, "renderError");
            vnode = vm._vnode;
          }
        } else {
          vnode = vm._vnode;
        }
      }
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if ("development" !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        );
      }
      vnode = createEmptyVNode();
    }
    // set parent
    vnode.parent = _parentVnode;
    return vnode
  };

update


//当依赖项改变时调用。前面有提到。
Watcher.prototype.update = function update () {
    var this$1 = this;

  /* istanbul ignore else */
  //是否计算属性
  if (this.computed) {
    if (this.dep.subs.length === 0) {
      this.dirty = true;
    } else {
      this.getAndInvoke(function () {
        this$1.dep.notify();
      });
    }
    //是否缓存
  } else if (this.sync) {
   //调用run方法执行回调函数
    this.run();
  } else {
    queueWatcher(this);
  }
};

Watcher.prototype.run = function run () {
  if (this.active) {
    //这里的cb就是指watcher的回调函数
    this.getAndInvoke(this.cb);
  }
};

Watcher.prototype.getAndInvoke = function getAndInvoke (cb) {
  var value = this.get();
  if (value !== this.value ||isObject(value) ||this.deep) {
    //设置新的值
    var oldValue = this.value;
    this.value = value;
    this.dirty = false;
    if (this.user) {
      try {
        cb.call(this.vm, value, oldValue);
      } catch (e) {
        handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
      }
    } else {
      //执行回调函数
      cb.call(this.vm, value, oldValue);
    }
  }
};

后记

关于Vue数据双向绑定的文章很多,查看越多资料越觉得自己只是浅薄,要更努力才行啊。
学习到比较系统思路的来自于:https://segmentfault.com/a/11...
十分感谢

来源:https://segmentfault.com/a/1190000016208088

posted @ 2018-12-19 17:50  大天狗子  阅读(5564)  评论(0编辑  收藏  举报