Let’s take a look at how Vue handles data. The following Vue source code comes from V2.6.12.

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

initData

function initData (vm) {
  // 1. Get the object returned by the data option and assign it to data & vm._data
  _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. Check whether there is an attribute with the same name as data in the methods option
  // 2. Check whether the data option has an attribute whose name is the same as that of props
  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); }}// Start observing 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);
}
Copy the code

observe

/** * 1. If value is not an object or array, or is a VNode instance, end the observation * 2. If it has already been observed, return the existing Observer instance * 3. Otherwise, create an Observer for Value and return it */
function observe (value, asRootData) {
  if(! isObject(value) || valueinstanceof VNode) {
    // Value is not an object, array, or VNode instance
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    // Value has an __ob__ attribute and is an observer instance that has been observed and returns __ob__ directly
    ob = value.__ob__;
  } else if( shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) && ! value._isVue ) {// Create an observer instance
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}
Copy the code

Observer

/** * 1. First create an observer instance and add it to each observed object & array. * 2. If it is an array, replace the __proto__ of the original array and iterate over the elements to observe * 3. If it is an object, traverse the object properties to observe */                                                                                                                                                
var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value, '__ob__'.this); // Add observer instance __ob__
  if (Array.isArray(value)) {
    // value is an array
    if (hasProto) { __proto__ is not a standard attribute, so some browsers do not support it
      // If __proto__ is supported, replace the array prototype
      protoAugment(value, arrayMethods);
    } else {
      copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);
  } else {
    // value is an object
    this.walk(value); }};/** * sets the response for each item in the array, handling the case where the array elements are objects & arrays */
Observer.prototype.observeArray = function observeArray (items) {
  for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); }};/** * iterates over each key on the object, setting a responsive */ for each 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
  });
}
Copy the code
  • The value is an array

    / / initial value
    value = ['12', { num: 13 }] 
    // Add an observer object __ob__ to value
    value = ['12', { num: 13 }, __ob__]
    // Since value is an array, the array's __proto__ needs to be replaced
    value = ['12', { num: 13 }, __ob__, __proto__: [ pop/push/..., __proto__: Array.prototype]]
    Value [1] is an object, so create an observer object for value[1]
    value = ['12', { num: 13, __ob__ }, __ob__, __proto__: [ pop/push/..., __proto__: Array.prototype]]
    // Set the value[1] property to reactive
    value = ['12', { num: 13, __ob__, getNum, setNum }, __ob__, __proto__: [ pop/push/..., __proto__: Array.prototype]]
    Copy the code

    Be rewritten the array of prototype method are: pop/push/reverse/shift/sort/splice/unshift

    DefineProperty adds an element to the array. The length of the array is not incremented by 1. Array.prototype and elements in the replaced __proto__ (that is, prototype methods) are defined via Object.defineProperty.

  • The value is an object

    / / initial value
    value = { num: 13 }
    // Add an observer object __ob__ to value
    value = { num: 13, __ob__ }
    // Since value is an object, set its property to reactive
    value = { num: 13, __ob__, getNum, setNum }
    Copy the code

defineReactive$$1

/** * sets the response */ for the properties of the object
function defineReactive$$1 (obj, key, val, customSetter, shallow) {
  // Create a new Dep instance for each property of the object (obj[key]). This instance will be closed in the getter & setter of the property
  var dep = new Dep();
	
  // Get the property descriptor object for obj[key]. If it is set to unconfigurable, no response is set for this property
  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // Get the pre-defined getter/setter and val
  var getter = property && property.get;
  var setter = property && property.set;
  if((! getter || setter) &&arguments.length === 2) {
    val = obj[key];
  }
	
  // Call obj[key] recursively, handling the case where the value of obj[key] is an object & array, ensuring that the nested object properties are also set to responsiveness
  varchildOb = ! shallow && observe(val);// Responsive core
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      
      /** * dep. target is a static attribute of the Dep class with a value of watcher; * When user watcher and Render Watcher are instantiated, dep. target is set to the user watcher and Render Watcher being instantiated. Except for computed watcher * for user Watcher, for example: * watch: {* name(newVal, oldVal) {* console.log('name changed') *} *} * generates a getter function for name, similar to vm => vm.name. Add watcher to the deP of the name closure by firing the getter for name. * When updateComponent is executed, it accesses props, data, etc., such as name, and triggers the getter for name. Add Render Watcher to the deP of the name closure * after collecting dependencies, set dep. target to null to avoid repeating dependency collection here
      if (Dep.target) {
        // Dependency collection, add the current watcher to the dep.subs of the closure, and also add the dep to the watcher.deps
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) { dependArray(value); }}}return value
    },
    set: function reactiveSetter (newVal) {
      / / the old value
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      // If the old value is the same as the new value, return without updating the old value or triggering a reactive update
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      /* eslint-enable no-self-compare */
      if (customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      // If the setter does not exist, the property is read-only and returns directly
      if(getter && ! setter) {return }
      // Set the new value
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      // Set the new value to reactivechildOb = ! shallow && observe(newVal);// Rely on notification updatesdep.notify(); }}); }Copy the code

Data responsive

Let’s take a look at the change process of data and the final data structure of data after reactive processing:

reference

Vue source code interpretation (3) – responsive principle

Vue source analysis series of articles

  • Vue source analysis – initData
  • Vue source code analysis – initComputed