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