After analyzing the entire initialization process of the VUE Demo in the previous seven chapters, this article begins by analyzing the vUE process when the data (model) changes.
We analyze the UPDATE phase of Vue by clicking on dom to trigger plus to execute this.info.age++.
proxyGetter && proxySetter
IninState ->initData->proxy(VM, “_data”, key)
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
Where this is the Vue instance and sourceKey is _data, that is, the key in the _data object is returned. IninState ->initData->observe->walk->defineReactive? ReactiveGetter in 1:
reactiveGetter && reactiveSetter
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter() {
var 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) {
var value = getter ? getter.call(obj) : val;
if(newVal === value || (newVal ! == newVal && value ! == value)) {return;
}
if (true && customSetter) {
customSetter();
}
if(getter && ! setter) {return;
}
if (setter) {
setter.call(obj, newVal);
} else{ val = newVal; } childOb = ! shallow && observe(newVal); dep.notify(); }});Copy the code
Dep.target (value) does not exist because watcher update is not being performed.
Then read this.info.age, which directly triggers reactiveGetter to return value.
At the end of the read phase, reactiveSetter is triggered to set the new value of this.info.age.
childOb = ! shallow && observe(newVal); dep.notify();Copy the code
After listening for the new value, the corresponding subscriber center Dep is notified of the variable.
Dep.prototype.notify
Dep.prototype.notify = function notify() {
var subs = this.subs.slice();
if (true && !config.async) {
subs.sort(function (a, b) {
return a.id - b.id;
});
}
for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};Copy the code
Where subs is the subscription list of variables. In this case, the variable this.info.age has three subscriber watcher, one from watch Watcher, one from render watcher, and one from calculate Watcher.
In Notify, if synchronization is configured, the subscription list subs are sorted by ID size. The update method for each item (watcher) is then triggered in turn.
Watcher.prototype.update
Watcher.prototype.update = function update() {
if (this.lazy) {
this.dirty = true; // Set the identifier bit for calculating watcher
} else if (this.sync) {
this.run(); // If synchronization is set, it is executed immediately
} else {
queueWatcher(this); // Take the current instance as an argument}};Copy the code
Watcher.prototype.update focuses on some processing of the configuration and then passes the current Watcher to queueWatcher.
Watcher is computed as lazy (that is, this.lazy is true), so queueWatcher is not executed, but the identifier bit this.dirty is set to true. Attribute values in computedGetter behind, it is calculated through Watcher. Prototype. The evaluate to recalculate the value rather than directly from the cache values, and will also identify an enclosing dirty set to false, If the watcher update is not triggered by any other value update in the expression corresponding to the calculated attribute, the value can be directly fetched.
queueWatcher
function queueWatcher(watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true; // Prevent multiple pushes from the same watcher at the same time
if(! flushing) { queue.push(watcher);// Whether to flushSchedulerQueue is executed
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1.0, watcher);
} // queue the flush
if(! waiting) { waiting =true; // Prevent multiple nextTick, that is, prevent multiple registration of new microtask queues
if (true && !config.async) {
flushSchedulerQueue();
return;
}
nextTick(flushSchedulerQueue); Pass the flushSchedulerQueue function into nextTick}}}Copy the code
has
Is an object used to filter the samewatcher
Multiple calls at the same timeupdate
queue
Is an array that holds data waiting to be updatedwatcher
The queueflushing
Is a Boolean value that identifies whether the queue is performing updates untilqueue
Reset to after the execution is completefalse
flushSchedulerQueue
The execution function for the update queuewaiting
Is a Boolean value used to indicate that theflushSchedulerQueue
Register to the task queue for the next microloop untilqueue
Reset to after the execution is completefalse
In queueWatcher, we push the current Watcher instance into a queue, and pass the flushSchedulerQueue into the nextTick method.
nextTick
function nextTick(cb, ctx) {
var _resolve;
// Callbacks save the queue of asynchronously executed tasks
callbacks.push(function () {
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick'); }}else if(_resolve) { _resolve(ctx); }});if(! pending) { pending =true;
timerFunc();
} // $flow-disable-line
if(! cb &&typeof Promise! = ='undefined') {
return new Promise(function (resolve) { _resolve = resolve; }); }}Copy the code
In nextTick, the asynchronously executed task queue CB is uniformly collected into the Callbacks array (flushSchedulerQueue is one item) and timerFunc is executed.
TimerFunc executes flushCallbacks asynchronously. Resolve (). Then >MutationObserver>setImmediate>setTimeout ().
NextTick (flushSchedulerQueue) : async wait; dep.prototype. notify: update the next watcher to queue.
In flushCallbacks, the method iterates through the callbacks and executes them.
Why would you do that
If there is no asynchronous queue, update->patch will be triggered every time the data changes to compare vNodes to update the DOM. If there is a large number of changes at the same time (watcher is triggered many times), the view will be updated frequently and repeatedly. With this queue, watcher repeatability can be determined on the current stack and repeated updates can be filtered out. Performance is greatly improved by ensuring that the action of updating the view to manipulate the DOM is called in the next tick event loop after the current stack completes execution.
flushSchedulerQueue
function flushSchedulerQueue() {
currentFlushTimestamp = getNow();
flushing = true;
var watcher, id;
queue.sort(function (a, b) {
return a.id - b.id;
});
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 (true&& 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
var activatedQueue = activatedChildren.slice();
var updatedQueue = queue.slice();
resetSchedulerState(); // call component updated and activated hooks
callActivatedHooks(activatedQueue);
callUpdatedHooks(updatedQueue); // devtool hook
if (devtools && config.devtools) {
devtools.emit('flush'); }}Copy the code
For this example, there are currently two watchers in the queue: Watch watcher, render watcher. FlushSchedulerQueue sorts the queue first:
- Component updates are parent to child (because the parent component is created before the child component), so
watcher
The creation and execution order of the line should also be the parent after the child - User defined
watcher
Should be inRender the watcher
Before executing (because of user customizationwatcher
The creation of theRender the watcher
Before) - If a component is in the parent component’s
watcher
During execution is destroyed, then the subcomponent’swatcher
Can be skipped (this.active
Logo).
Then execute the watcher. Before for each watcher in the queue to trigger the lifecycle beforeUpdate hook. Then execute watcher.run(), as analyzed below.
After execution, call resetSchedulerState to reset the state, call callActivatedHooks to change the component to the Activated state and trigger the lifecycle Activated hook, Call callUpdatedHooks to trigger the lifecycle Update hook, which triggers the tool’s Flush event, and the process ends.
Watcher.prototype.run
Watcher.prototype.run = function run() {
if (this.active) {
var value = this.get();
if(value ! = =this.value || isObject(value) || this.deep) {
// set new value
var oldValue = this.value;
this.value = value;
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, 'callback for watcher "' + this.expression + '"'); }}else {
this.cb.call(this.vm, value, oldValue); }}}};Copy the code
This. Active is used to identify whether the watcher has been uninstalled. The Watcher. Prototype. Set to false in the teardown (unloaded).
Watcher.prototype.run executes this.get to get value assigned to this.value. Then update watcher’s value. If this.user is true and represents a user-defined watch, execute cb, the user-defined callback method, and watch Watcher will trigger the callback.
Watcher.prototype.get
Watcher.prototype.get = function get() {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, 'getter for watcher "' + this.expression + '"');
} else {
throwe; }}finally {
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value;
};
Copy the code
Watcher. Prototype. Mainly in the get through this. Getter get value.
Dep. Target = Dep. Target = Dep. Target = Dep.
If deep is set to true, traverse recursively executes each child property of the _traverse read object to add the watcher subscription to them.
-
Where the getter for watch Watcher is:
/ /... return function (obj) { for (var i = 0; i < segments.length; i++) { if(! obj) {return; } obj = obj[segments[i]]; } return obj; }; / /... Copy the code
Obj [segments[I]] [segments] [segments]] [segments] [segments] [segments] [segments] [segments] [segments] [segments] [segments] [segments] [segments] [segments] So it’s also added to info.__ob__.
-
Where the getter for rendering watcher is:
updateComponent = function () { vm._update(vm._render(), hydrating); }; Copy the code
Vm. _render gets vNodes based on the render function. During render, every variable read will trigger its corresponding proxyGetter->reactiveGetter to get the latest value and the render Watcher will subscribe to the corresponding variable’s subscription list. Once you have the VNode, then execute vm._update to update the view, which is analyzed in the next chapter.
Finally, the cleanupDeps method is executed to delete invalid subscription subs by comparing the old and new depIds. Finally, value is returned to the flushSchedulerQueue method.
The summary of this chapter
- This chapter starts with a data update and analyzes the logic associated with adding subscriptions to data and triggering subscriptions.
- There are three of them
watcher
Are:watch watcher
.Render the watcher
.Calculate the watcher
For differentwatcher
It’s done differently. - Triggering subscriptions involves asynchronous queue optimization that optimizes unnecessary view updates, greatly improving performance.