Vue is data-driven, meaning that data changes drive view updates. To do this, you first need to monitor the data

Initialize the

Vue initializes the instance by calling _init, where initState is called to initialize the data

export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options? : Object) { // ... Slightly initState (vm)}}Copy the code

initState

InitState defined in SRC/core/instance/state. Js

export function initState(vm: Component) { vm._watchers = []; const 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); }}Copy the code

You can see that in this method, props Methods data computed Watch is initialized. Because data is driven primarily for data in data, let’s look at initData

initData

InitData defined in SRC/core/instance/state. Js

function initData(vm: Component) { let data = vm.$options.data; data = vm._data = typeof data === "function" ? getData(data, vm) : data || {}; if (! isPlainObject(data)) { data = {}; process.env.NODE_ENV ! == "production" && warn( "data functions should return an object:\n" + "https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function", vm ); } const keys = Object.keys(data); const props = vm.$options.props; const methods = vm.$options.methods; let i = keys.length; while (i--) { const 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); } } // observe data observe(data, true /* asRootData */); }Copy the code

When using Vue, data is usually defined as a method (for different Pointers to data), so initData starts with a judgment. If data is a function, call it to get data.

If data is not an object, it is initialized as an object.

It then makes a name-check and warns if it is the same name as an attribute in methods props

Finally, initData does two main things

1. Proxy-proxy properties

What is a proxy attribute? For example, if a MSG attribute is defined in data, why can we use this. MSG directly instead of this.data.msg when referencing it? The reason is that when initData is used, Vue proxies the properties of data to this, and other props, computed methods, and so on, are also used to access this directly

{
  data () {
    return {
      msg: 'hello'
    }
  },
  methods: {
    say() {
      console.log(this.msg)
    }
  }
}
Copy the code

The implementation is simple enough to see a key line of code in initData

proxy(vm, `_data`, key);
Copy the code

DefineProperty proxy intercepts getter reads and setter Settings for this via Object.defineProperty. When a proxy reads a property on this._data, it sets a property on this._data

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function proxy (target: Object, sourceKey: string, key: string) {
  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

2. observe-monitoring data

You can see that initData is finally executed

observe(data, true /* asRootData */);
Copy the code

Observe defined in SRC/core/observer/index, js

export function observe (value: any, asRootData: ? boolean): Observer | void { if (! isObject(value) || value instanceof VNode) { return } let ob: Observer | void 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 }Copy the code

If the data is monitored, then there is an __ob__ attribute, which is simply assigned to OB; if not, an Observer class is instantiated and data is passed in as an argument

Observer class defined in SRC/core/Observer/index. Js

export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } /** * Walk through each property and convert them into  * getter/setters. This method should only be called when * value type is Object. */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } /** * Observe a list of Array items. */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }Copy the code

Observe that the Observer adds an __ob__ attribute to the data, pointing to itself. This corresponds to the observation in observe

Then we call the observeArray method to monitor the array, which actually traverses the array and monitors each item

For objects, the walk method is called directly, and you can see that defineReactive is called in the walk method to traverse the object

DefineReactive defined in SRC/core/observer/index, js

export function defineReactive ( obj: Object, key: string, val: any, customSetter? :? Function, shallow? : boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((! getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = ! shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const 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() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = ! shallow && observe(newVal) dep.notify() } }) }Copy the code

The Dep is instantiated for each attribute in data. This is used to collect the current watcher for the current attribute. The watcher is used to initialize the view. It also contains an update method for the view, which collects the Watcher and calls the update method to drive the view update when the data is updated

The observe method is then called on the property, which is equivalent to recursively monitoring the object property in data

Finally, the key code comes in. DefineReactive intercepts the getter and setter operations for the property using Object.defineProperty

Collect the current Watcher in the getter. The key code here is dep.depend(). This line of code subscribes to the current Watcher using the observer system

Update the view in the setter, and the key code here is the dep.notify() line that notifies the Watcher in the observer queue to update the view

conclusion

As shown in the figure, the Vue monitor data is initialized using Object.defineProperty to intercept the getter and setter operations of the data. In the getter, the observer system DEP is used to collect the current watcher. Notify the Watcher in the queue in the setter to update the view

Collection process Update process

How does the observer system work with watcher in getter to collect dependencies

As well as

How do SETters tell Watcher to update views

Send 😂 again next time