An overview of responsive principles
Instantiate Vue, call _init, initialize data, methods, watch, props, lifecycle, etc., call observer function observer to generate proided object, object will have an __ob__ attribute which is an instance of observer. This means that the object is propped, and each property in the propped object passes defineReactive? Method 1 (recursively down if the property value is an Object or array). Object proxy is implemented using the native API Object.defineProperty. Object.defineproperty defines the get and set methods, respectively. The GET method triggers dependency collection when an Object property is referenced, and the set method is used to modify the property and perform the update method. In the $mount method, the template is parsed, the dependency collection is triggered by reference to object properties to generate a vNode, and the real view is rendered. Later, when the property is updated, the set method is triggered to update it.
Analysis from the source point of view
Initialize instance
_init
function Vue (options) {
if(! (this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
Copy the code
_init initializes the Vue instance, _init is defined on the prototype chain, see vue.prototype. _init, where initLifecycle determines the component hierarchy, parent component, root component, define properties to hold the child component, wather object, record life cycle execution state properties, etc. InitEvents does some of the operations associated with parent listener functions; InitRender, which initializes properties and methods related to the virtual DOM.
injection
The initInject attribute is the same as the props attribute. The inject format should be an array of strings or objects. In the resolveInject function, if it is an array, each item corresponds to the attribute in provide. If it is an object, then determine its attribute value type. If it is an object, a default attribute should be included to return the value of the default attribute; otherwise, the value corresponds to the attribute in provide. Simultaneously injected properties are queried upwards until either a provide containing the property is found or until the root component is not found. The generated object will be injected into the current Vue instance. During the injection process, it will use toggleObserving to change the shouldObserve state to false. The injected object will not recursively act as a proxy down. InitState initializes properties such as data, props, methods, computed, and watch. This is also where Vue implements the principle of responsiveness, so let’s put it down and introduce injection first. InitProvide provides for the initInjections method above.
See the order in which these functions are executed
initInjections => initState => initProvide
InitState is first put after initInjections, which is to prevent the properties in data from being overwritten, while initProvide is executed last because it is the data provider and the outermost layer doesn’t need injection. Inject in the descendant component can access the outermost Provide by looking up the ancestor component.
Responsive implementation
Going back to the initState method, the most important of these is the object proxy for data. The observe method, observer as the constructor, and data as the argument generate an observer. The value property holds data. The proxy object uses the __ob__ property to hold the observer instance. Then call the method walk on the prototype, using defineReactive? 1 Proxy each property in the data object.
defineReactive? 1
defineReactive? 1 First generate a Dep subscriber, then determine if the property is configurable, then determine if the property value is an Object or array and further construct the observer Object, then object.defineProperty method, which is also the API that allows Vue to implement the responsivity principle. DefineProperty is intended to define attribute modifiers, including modifiability, configurability and enumerability of attributes. It is also possible to define get and set methods, which are executed when attributes are read and modified, respectively. The get method performs dependency collection based on the value of the property, and the set method informs all Watcher observers that depend on the deP of the property to update the view.
After a brief introduction to Define Active? 1. Take a closer look at the two key things in this function: Dep and Watcher
Dep
Dep. Target a global variable that points to the current watcher observer. Then, when this attribute is referenced in a view, The current Watcher observer object is added to the subs in the DEP subscriber of the property. When the property is updated, the notify method in the corresponding DEP is called to notify all the Watcher in the subs to update the view.
Watcher
The observer constructor, as described above, has an _watcher attribute for each Vue instance that corresponds to a Watcher observer. The dependency collection procedure below covers watcher in more detail. This should be described by a diagram: object proxy, Dep, Watcher, dependency collection, virtual DOM, render view, etc.
Depend on the collection
Created callback ($mount); var. Prototype. $mount (); var. Prototype. $mount (); var. Prototype. $mount (); Var mount = Vue. Prototype.$mount; $mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount As you can see, some checks are done to see if Render exists, if not, a default method is assigned (creating a virtual DOM containing only text) and a warning is thrown. The beforeMount lifecycle function is then executed, and an updateComponent method is first defined, which triggers dependency collection and generates vNodes based on references to object properties.
We’re going to new a Watcher object, and the updateComponent method is going to be passed in as the second argument. See how Watcher defines it next.
var Watcher = function Watcher (vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
// options
if (options) {
this.deep = !! options.deep;this.user = !! options.user;this.lazy = !! options.lazy;this.sync = !! options.sync;this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb;
this.id = ++uid$2; // uid for batching
this.active = true;
this.dirty = this.lazy; // for lazy watchers
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.', vm ); }}this.value = this.lazy
? undefined
: this.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 {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
Copy the code
Initialize a set of properties and methods. When constructing a Watcher object, the get method on the prototype is called. PushTarget changes dep. target to the current wathcer, and the getter method on the object is called. The getter is the previous updateComponent method, and the updateComponent is actually executed, calling the _render method on the Vue instance.
Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
var render = ref.render;
var _parentvNode = ref._parentvNode;
/ /... Elliptic segmentation......
var vNode;
/ /... Elliptic segmentation......
vNode.parent = _parentvNode;
return vNode
Copy the code
After a series of operations, the dependency collection and return of the corresponding vNode, namely the virtual DOM, vNode has an important property parent node, if the parent node is empty, it means that the node is the root node, after the virtual DOM, _update, __patch__, etc., mainly diff algorithm part, The popTarget method is called to return dep. target to the previous value before the updateComponent execution ends.
Dep.target = null;
var targetStack = [];
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
Copy the code
Why use an array targetStack to hold the watcher, which is related to the parent component mount update rule, Parent beforeCreate => parent created => parent beforeMount => child beforeCreate => child created => child beforeMount => child Mounted => parent Mounted. This is the mount phase parent component lifecycle execution order. Because child dependency collection occurs after beforeMount and before Mounting, the child builds a new Watcher object if the parent component has a child before the parent dependency collection ends. PushTarget pushes the parent component’s watcherinto the targetStack and is deferred. After the child component generates a vNode, popTarget reverts to the parent component’s Watcher.
Watcher.prototype.cleanupDeps = function cleanupDeps () {
var i = this.deps.length;
while (i--) {
var dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this); }}var tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
};
Copy the code
As for the cleanupDeps method, which is used to update the Watcher object in the DEP subscriber to which the property corresponds and does not need to rely on this property in the view, the Watcher is removed from the corresponding DEP and the newDepIds in the Watcher store the latest SET of DEP ids each time. For example, for v-if controlled node components, when the showChild value changes from True to false, cleanupDeps will unsubscribe childVal after executing since the child component no longer needs to render.
<Parent>
<Child v-if="showChild">{{childVal}}</Child>
</parent>
Copy the code
View update
Once the updateComponent execution is complete, the view is rendered. When the data of an object agent is updated, set method is executed, deP triggers notify method to notify all watcher observers to perform update, generate new VNodes, re-collect dependencies, etc. Patch diff the new and old VNodes and update the view of the difference part.
Why are you still writing a summary of Vue2 when Vue3 is out? Harm, I feel now do not sort out later more no opportunity 🐶. Hey hey, there are mistakes also hope to dig friends pointed out.