Vue source code analysis
1. Principle analysis of data response
1 Key Principles
Object.defineproperty, which adds listening dependencies from the GET method and triggers dependencies from the set method
2 combined with the source code, detailed analysis
-
When vue is initialized, the initData function is triggered and the value of this.$option.data is not an object, but a function. Check whether the methods and props have declared the value of the data object, and then use the observe function to listen. observe(data, true /* asRootData */)
// state.js function initData (vm: Component) { let data = vm.$options.data // Get vue data Data.call (vm,vm) returns an object // {name: 'xiaoming '} 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 ) } // proxy data on instance 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 Perform data monitoring observe(data, true /* asRootData */)}Copy the code
-
Observe new observe (value) the observe function examines whether data has been observed.
// observe/index.js export function observe (value: any, asRootData: ? boolean) :Observer | void { Return if data is not an object if(! isObject(value) || valueinstanceof VNode) { return } let ob: Observer | void // Whether the listener is already marked 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 ) {// If there is no observation, the observation function is triggered ob = new Observer(value) } if (asRootData && ob) { // count up ob.vmCount++ } return ob } Copy the code
-
Observe class, the instantiated registers Dep () as an example, Array. The isArray (value) to determine whether Array, is performing observeArray, or traverse Observe function, deep observation arrays. The object calls defineReactive(obj, key) directly, which redefines the object data
// observe/index.js class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__'.this) // array. isArray(value) Define defineReactive(obj, key) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) // Observe the array in depth } else { this.walk(value) } } /** * Walk through all properties 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]) // Redefine the object type -> reactive data}}/** * 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
-
DefineReactive Redefines responsive data, defines Object.defineProperty, triggers dep.depend when data is retrieved, and triggers dep.notify() when data is modified.
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow? : boolean) { const dep = new Dep() // instantiate a Dep, monitor 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] } letchildOb = ! shallow && observe(val)// Recurse, if it is still an object, continue with observe Object.defineProperty(obj, key, { enumerable: true.configurable: true.get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() // Collection depends on Watcher if (childOb) { childOb.dep.depend() // Collection depends on Watcher 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() } // #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()// Trigger the corresponding numeric update}})}Copy the code
-
Solve the problem.
Why is dep.target judged here?
If (dep.target) {dep.depend() // Collect dependent watcher if (childOb) {childob.dep.depend () // Collect dependent watcher if (array.isarray (value)) {dependArray(value)}}} // The 'dep. target' is' true 'because' notify() 'called' pushTarget 'function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target }Copy the code
3 summary
How are 2 arrays monitored
1 Key Principles
- Overrides the array method on the VUE prototype chain
push
,pop
And so on in the call arrayAPI
Data monitoring will be performed to notify dependencies of updates. If the array contains a reference type, the reference type is monitored.
2 combined with the source code, detailed analysis
-
Observe: If a new Observer(value) is used, value is evaluated. If an array is used, array operations are overwritten
// observe/index.js // Observe in class if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) // arrayMethods are methods for overriding arrays } this.observeArray(value) // Deeply traverse the array. If there are still objects in the array, go on } else { this.walk(value) } / / observeArray method observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } Copy the code
-
Dep.notify () is triggered when the array method is called to update the view
// observe/array.js const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse' ] /** * Intercept mutating methods and emit events */ // Rewrites the original array method, preserves the original method, expands the observation. Manually call dep.notify() to update the view methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (. args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) }) Copy the code
Vue asynchronous rendering principle
Characteristics of 1.
Vue, without asynchronous updates, rerenders components every time data is updated, causing performance problems.
2. Combined with source code, detailed analysis
-
Dep.notify (), which triggers data updates to notify Watcher
/ / dep. Js // Execute the update method in watcher notify () { // stabilize the subscriber list first const subs = this.subs.slice() if(process.env.NODE_ENV ! = ='production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) = > a.id - b.id) } // Iterate through subs to trigger update in watcher.js // Subs contains multiple watchers, which are triggered simultaneously. for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } Copy the code
-
The Update in the watcher class fires, putting the watch in the queueWatcher queue
update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this)}}Copy the code
-
QueueWatcher is a filter watcher that distinguishes between push to queue and remove from queue based on the plushing state. NextTick (flushSchedulerQueue)
function queueWatcher (watcher: Watcher) { const id = watcher.id // Filter the id of watcher, if it exists, do not process, if it does not exist, mark if (has[id] == null) { has[id] = true if(! flushing) {// If the state is! The flushing goes into the queue queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1.0, watcher) // Flushing is removed } // queue the flush if(! waiting) { waiting =true if(process.env.NODE_ENV ! = ='production' && !config.async) { flushSchedulerQueue() return } nextTick(flushSchedulerQueue) } } } Copy the code
-
‘nextTick(flushSchedulerQueue) executes watcher.run()
function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. queue.sort((a, b) = > a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { watcher.before() } id = watcher.id has[id] = null watcher.run() // in dev build, check and stop circular updates. if(process.env.NODE_ENV ! = ='production'&& has[id] ! =null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break}}}// keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush')}}Copy the code
3 summary
- Call notify() to tell Watcher to update
- Call watcher’s update method in turn
- Deredo watcher (by ID) and put it on the queue
- After the command is executed, the queue is asynchronously cleared and the nextTick(flushSchedulerQueue) queue is updated in batches