Vue 源码分析 - initData

1,694 阅读3分钟

假设创建 vm 时的 data 选项如下,我们来看下 Vue 是怎么处理 data 数据的,以下 Vue 源码来自 v2.6.12。

data() {
  return {
    name: 'irene',
    addr: {
      prov: 'guangdong',
      city: 'shenzhen'
    },
    hobbies: ['dance', 'sing']
  }
}

initData

function initData (vm) {
  // 1. 获取 data 选项返回的对象,赋值给 data & vm._data
  // 2. 此时:data = vm._data = {name: 'irene', addr: {prov: 'guangdong', city: 'shenzhen'}, hobbies: ['dance', 'sing']}} 
  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
    );
  }
  // 1. 判断 data 选项中是否存在和 methods 重名的属性,有则报错
  // 2. 判断 data 选项中是否存在和 props 重名的属性,有则报错
  // 3. 代理 data 选项中的属性到 vm,即 vm.name -> vm._data.name; vm.addr -> vm._data.addr
  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);
    }
  }
  // 开始观察 data
  observe(data, true /* asRootData */);
}

var sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};
function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

observe

/**
 * 1. 如果 value 不是对象或数组,或者是 VNode 实例,直接结束观察
 * 2. 如果已经观察过,直接返回已存在的观察者实例
 * 3. 否则,为 value 创建观察者实例(Observer)并返回它
 */
function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    // value 不是对象或数组或者是 VNode 实例,直接结束观察
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    // value 有 __ob__ 属性且是观察者实例,表示已经观察过,直接返回 __ob__
    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
}

Observer

/**
 * 1. 首先创建观察者实例,添加到每个被观察的对象 & 数组。
 * 2. 如果是数组,替换原有数组的 __proto__,遍历元素进行观察
 * 3. 如果是对象,遍历对象属性进行观察
 */                                                                                                                                                
var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value, '__ob__', this); // 添加观察者实例 __ob__
  if (Array.isArray(value)) {
    // value 是数组
    if (hasProto) { // 判断对象是否存在 __proto__ 属性,由于 __proto__ 不是标准属性,所以有些浏览器不支持
      // 如果支持 __proto__,将数组的原型替换
      protoAugment(value, arrayMethods);
    } else {
      copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);
  } else {
    // value 是对象
    this.walk(value);
  }
};

/**
 * 遍历数组,为数组的每一项设置响应式,处理数组元素为对象 & 数组的情况
 */
Observer.prototype.observeArray = function observeArray (items) {
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
};

/**
 * 遍历对象上的每个 key,为每个 key 设置响应式
 */
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive$$1(obj, keys[i]);
  }
};

/**
 * Define a property.
 */
function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}
  • value 是数组

    // 初始值
    value = ['12', { num: 13 }] 
    // 为 value 添加一个观察者对象 __ob__
    value = ['12', { num: 13 }, __ob__]
    // 由于 value 是数组,需替换数组的 __proto__
    value = ['12', { num: 13 }, __ob__, __proto__: [ pop/push/..., __proto__: Array.prototype]]
    // 遍历数组元素,如果是数组或对象,继续处理,value[1] 是对象,所以为 value[1] 创建一个观察者对象
    value = ['12', { num: 13, __ob__ }, __ob__, __proto__: [ pop/push/..., __proto__: Array.prototype]]
    // 将 value[1] 的属性设置成响应式
    value = ['12', { num: 13, __ob__, getNum, setNum }, __ob__, __proto__: [ pop/push/..., __proto__: Array.prototype]]
    

    被重写的数组原型方法有:pop/push/reverse/shift/sort/splice/unshift

    使用 Object.defineProperty 为数组新增一个元素,数组的 length 不会加 1;Array.prototype 以及被替换后的 __proto__ 里的元素(即原型方法)都是通过 Object.defineProperty 定义的。

  • value 是对象

    // 初始值
    value = { num: 13 }
    // 为 value 添加一个观察者对象 __ob__
    value = { num: 13, __ob__ }
    // 由于 value 是对象,将其属性设置成响应式
    value = { num: 13, __ob__, getNum, setNum }
    

defineReactive$$1

/**
 * 为对象的属性设置响应式
 */
function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  // 为对象的每一个属性(obj[key])新建一个 Dep 实例,这个实例会被闭包在属性的 getter & setter 中
  var dep = new Dep();
	
  // 获取 obj[key] 的属性描述符对象,如果被设置为不可配置,则不为这个属性设置响应式
  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // 获取事先定义的 getter / setter 以及 val
  var getter = property && property.get;
  var setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }
	
  // 递归调用,处理 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 为 Dep 类的一个静态属性,值为 watcher;
       * 在实例化 user watcher 和 render watcher 时 Dep.target 会被设置成正在实例化的 user watcher 和 render watcher,computed watcher 除外
       * 对于 user watcher,例如:
       *    watch: { 
       *      name(newVal, oldVal) {
       *        console.log('name changed')
       *      }
       *    }
       *    会为 name 生成一个 getter 函数,类似 vm => vm.name,执行该函数,从而触发 name 的 getter 函数,将 watcher 添加到 name 闭包的 dep 中去
       * 对于 render watcher(可简单理解为 template 创建的 watcher):
       *    当执行 updateComponent 函数时,会访问到 props, data 等,比如 name,从而触发 name 的 getter 函数,将 render watcher 添加到 name 闭包的 dep 中去
       * 收集完依赖后,将 Dep.target 设置为 null,避免这里重复收集依赖
       */
      if (Dep.target) {
        // 依赖收集,在闭包的 dep.subs 中添加当前 watcher,也在 watcher.deps 中添加 dep
        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 */
      // 如果新旧值一样,直接 return,不更新旧值也不触发响应式更新
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      // setter 不存在说明该属性是一个只读属性,直接 return
      if (getter && !setter) { return }
      // 设置新值
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      // 设置新值为响应式
      childOb = !shallow && observe(newVal);
      // 依赖通知更新
      dep.notify();
    }
  });
}

data 响应式

我们来看下经过响应式处理, data 的变化过程,以及 data 最终的数据结构:

image.png

参考

Vue 源码解读(3)—— 响应式原理

Vue 源码分析系列文章