关于vue.js:Vue-data响应式源码解析

45次阅读

共计 8812 个字符,预计需要花费 23 分钟才能阅读完成。

1. 获取组件选项的 data,而后调用 data 函数(this 和参数都指向以后组件),返回 data 数据保留到组件 _data 属性上,再由组件代理 _data 属性上的数据。最初调用 observe 实现 data 数据的响应式。

如果组件选项中的 data 是对象而不是函数,当存在多个该组件时,援用的是雷同的组件选项对象,组件操作 data 会相互影响。组件选项中的 data数据之间也不能通过 this 相互援用,因为先创立 data 数据对象,而后组件才代理 data 数据。

function initData (vm) {
  var data = vm.$options.data;
  data = vm._data = typeof data === 'function' // 赋值到组件实例_data 上
    ? getData(data, vm)// 调用 data 函数获取数据,data 函数内的 this 和参数都指向以后组件
    : data || {};
    ...
  // 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--) {// 判断是否和 methods 和 props 重名
    var key = keys[i];
    if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {
        warn(("Method \"" + key + "\" has already been defined as a data property."),
          vm
        );
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && 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);// 组件代理_data 数据(组件未代理之前)}
  }
  // observe data
  observe(data, true /* asRootData */);// 响应式设置
}

2. 在 observe 函数中,创立 Observer 实例,实现 data 数据的响应式。

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
}

3.Observer结构器中,将 Observer 实例保留到数据中,能够标识数据是否设置了响应式。判断数据类型是否为数组,依据判断后果进行不同的操作。

var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value, '__ob__', this);// 将 Obsserver 实例保留到数据中
  if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods); // 将数组__proto__指向 arrayMethods
    } else {copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);// 遍历数组,对数组元素持续调用 observe 办法
  } else {this.walk(value);
  }
};

4. 如果是数组,更改数组的 __proto__ 指向 arrayMethods,而arrayMethods__proto__指向 Array.prototypearrayMethods = Object.create(Array.prototype))。通过增加arrayMethods 中间层,代理数组办法,能够监测到数组的更新触发告诉。再调用 Observer 实例的 observeArray 办法,遍历数组,对数组元素持续调用 observe 办法。

function protoAugment (target, src) {
  /* eslint-disable no-proto */
  target.__proto__ = src;
  /* eslint-enable no-proto */
}
Observer.prototype.observeArray = function observeArray (items) {for (var i = 0, l = items.length; i < l; i++) {observe(items[i]);
  }
};
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);

var methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];

methodsToPatch.forEach(function (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); }//?为什么 push 时没有遍历设置响应式
    ob.dep.notify();// 告诉更新
    return result
  });
});

4. 如果数据不是数组是对象,则遍历对象,调用defineReactive$$1,实现对象属性的响应式。

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

5.defineReactive$$1函数中,每个对象属性都关联一个 Dep 实例,收集依赖(读取)该属性值的观察者。先对属性值持续调用 observe,再定义属性,增加属性的 get 和 set 办法。当读取该属性时,触发 get 办法,将以后观察者保留到Dep 实例上。当属性值批改时,触发 set 办法,对批改后的属性值调用 observe 办法,并告诉之前保留的观察者,从而实现数据的响应式。

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 在属性可能在设置可响应之前就存在 get 和 set
  var getter = property && property.get;
  var setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) { // 当不存在 get 或者存在 get 且没有提供 value 值时
    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(); // 将以后 watcher 收集到依赖中
        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 (process.env.NODE_ENV !== 'production' && 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();}
  });
}

6. 组件调用 mountComponent 办法,开始渲染组件页面。每个组件渲染都会创立一个观察者(Watcher 实例),页面更新函数保留到观察者的 getter 属性上。

// 开始渲染 dom
function mountComponent (
  vm,
  el,
  hydrating
) {
  vm.$el = el;
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode;
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      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');

  var updateComponent;
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== '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);// 更新页面
    };
  }

  // 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 */);
  hydrating = false;

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
  return vm
}

7. 该观察者在创立过程中,会调用 getter 函数,调用之前保留该观察者在 Dep.target 上。调用页面更新的 getter 函数会读取 data 中的数据,读取对象属性会触发属性的 get 函数,将 Dep.target 增加到对象属性关联的 Dep 实例上。调用实现之后移除保留在 Dep.target 上的观察者。

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 = process.env.NODE_ENV !== 'production'
    ? expOrFn.toString()
    : '';
  // parse expression for getter
  if (typeof expOrFn === 'function') {this.getter = expOrFn;} else {...}
  this.value = this.lazy
    ? undefined
    : this.get();};

/**
 * Evaluate the getter, and re-collect dependencies.
 */
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
};

8. 批改 data 数据时,会告诉观察者,触发观察者的 update 办法。调用 queueWatcher 将观察者增加到队列中,并在微工作中执行观察者的 getter 办法更新页面。

Watcher.prototype.update = function update () {
  /* istanbul ignore else */
  if (this.lazy) {this.dirty = true;} else if (this.sync) {this.run();
  } else {queueWatcher(this);
  }
};

9.Dep实例收集依赖数据的观察者,并在数据更新时告诉观察者。同时在 Dep 结构器上保留全局的观察者,用于数据读取时收集依赖它的观察者(Dep.target)。

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 * 观察者收集器,依赖收集器
 */
var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];// 观察者队列};

Dep.prototype.addSub = function addSub (sub) {this.subs.push(sub);// 增加观察者
};

Dep.prototype.removeSub = function removeSub (sub) {remove(this.subs, sub);// 移除观察者
};

Dep.prototype.depend = function depend () {if (Dep.target) {Dep.target.addDep(this);// 增加 Dep 实例到观察者中,不便观察者销毁时从实例中移除
  }
};

Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  if (process.env.NODE_ENV !== 'production' && !config.async) {subs.sort(function (a, b) {return a.id - b.id;});// 观察者从小达到排序
  }
  for (var i = 0, l = subs.length; i < l; i++) {subs[i].update();// 告诉观察者更新}
};

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null;// 保留在结构器上,全局观察者
var targetStack = [];// 堆栈,保留处于更新的观察者

function pushTarget (target) {targetStack.push(target);// 设置 target 观察者入栈
  Dep.target = target;// 设置 target 观察者为全局观察者
}

function popTarget () {targetStack.pop();// 以后观察者出栈
  Dep.target = targetStack[targetStack.length - 1];// 设置栈中最新的观察者为全局观察者
}

正文完
 0