preface

Using Vue frequently touches and uses the lifecycle in daily development, as explained in the official documentation:

Each Vue instance goes through a series of initialization procedures when it is created — for example, you need to set up data listeners, compile templates, mount the instance to the DOM, update the DOM when the data changes, and so on. Functions called lifecycle hooks are also run along the way, giving users the opportunity to add their own code at different stages.

Like the process of life and death,Vue also has a series of processes from build initialization to component mount, component update, and component destruction. The lifecycle hook is a function that lets developers do something when the Vue reaches a certain time

The most common is to send ajax requests in mounted hooks to retrieve data needed by the current page component

But for vue.js, just knowing the spelling of the life cycle and the corresponding trigger time is not enough. Why can’t the hook function be an arrow function? Why can’t we get defined data in data? How does Vue do event listening/event unbinding without awareness

In this article, I will take you into the source code of Vue and analyze the Vue life cycle from the source code

The source code screenshot in this article only preserves the core logicFull source code address

Vue version: 2.5.21

An overview of the source

When we instantiate a Vue in main.js, we do some logic and then go to the _init function to start the Vue life cycle. The way these functions are named gives us an idea of how Vue works

Merge configuration items

As you can see from the figure above, the first thing in the lifecycle is to merge configuration items, and Vue handles it differently for root and component instances (new in main.js) Vue generates the root instance, all other component instances), the root instance does not pass the _isComponent property in the options argument, instead, it will pass the true parameter.

Vue-router and VUex are not introduced for unnecessary interference

The root instance merges configuration items

Instance will go to the root of false logic into mergeOptions function, combined the Vue all configuration options, such as mixins, props, the methods, watch, computed, the life cycle of hooks, etc., this is the first merge configuration in the project. Vue stores all the merge policies in a Single Strats object and starts looking for the same property of the current instance and parent

You can see from the breakpoints that Strats saves many of the merged policies

We don’t need to go through every merger strategy, but try to focus on the whole process. The merger of the first, the Vue will pass resolveConstructorOptions (vm) constructor) for static properties of the Vue constructor options as a parent, the options contains some preset configuration items, Child is the argument we pass to the root instance when we instantiate it, corresponding to the render function in the above example

Vue pre-set configuration items as the first parent:

The root instance instantiates the passed argument:

The merge strategy of the root instance is actually very simple. It is mainly a simple merge of some built-in configuration items of the Vue framework and the parameters passed in by the developer to instantiate the Vue constructor in main.js, as the $options attribute of the root instance

Component instances merge configuration items

Component instance merge configuration items are not in the _init function, because unlike the root instance, component instances are instantiated by the component constructor, and the root instance is instantiated by the Vue constructor, which in turn inherits from Vue. It inherits from the Vue constructor through the vue.extend method, which I’ve drawn here to make it easier to understand

Vue does this in line with the object-oriented design pattern, where a component is essentially a constructor function (or, further, a class), so that introducing multiple identical components into a page requires only multiple instances of the component constructor, independent of each other

Another benefit of object orientation is inheritance, which is reflected in the Vue framework by inheriting the component constructor from the Vue constructor, so that the component constructor can obtain some configuration items built into the Vue constructor

Component instance merge configuration item SRC /core/global-api/extend.js, also called mergeOptions component instance merge configuration item merges the Vue framework built-in configuration item with the current component configuration item and assigns it to the static property Options of the component constructor

Back in mergeOptions, here is just one example of the lifecycle of the merge strategy, directly attached to the source code with a flow diagram for easy understanding

Here I used the parent rather than the parent component, because components of Vue generally inherit from Vue constructor rather than the parent component, through flow diagram can be found that the Vue will ensure life cycle function is always an array, and the father = > the son order, Vue when execute a life cycle will traverse the array in sequence function, So when we define a lifecycle function in both the Vue constructor and component constructor in the same lifecycle, the one in the Vue constructor is executed first

After inheriting the Vue constructor, it instantiates the child component to generate the component instance. Then it goes to the _init function, where _isComponent is true and it executes initInternalComponent, which creates the $options property for the component instance, Points to the static options property of the subcomponent constructor, so that configuration items of the current component can be accessed through the $options property of the component instance, as well as configuration items built into the Vue framework (including global components, global interfuse).

summary

  • The first thing in the life cycle is to merge configuration items, and there are different times for root and component instances to merge
  • The root instance merges the configuration items that are built into Vue with those that are passed in by new Vue
  • For the component instance, the constructor of the child component is created, and Vue. Extend is called to inherit the Vue constructor. When inheriting the Vue built-in configuration items and the component configuration items are merged, and the result is saved in the options property of the constructor. Then entering the initInternalComponent method during component instance creation points the component instance’s $Options to the component constructor’s Options property
  • The Vue framework implements different merge strategies based on different configurations

An error in the agent development environment

In non-production environments, the initProxy function is used to intercept VM instances through the ES6 Proxy to provide customized warnings for unreasonable configurations in the development environment

The above error is common to many developers. In this case, using the Proxy has interceptor, Vue will give a friendly warning when a property that is not on the VM instance is referenced by the template

Initializes custom events

And then you go to initLifecycle, there’s nothing to talk about, initialize some life cycle state of the instance and some extra properties, and then you go to a custom event that initializes the component, okay

InitEvents only mounts custom events, that is, non-native events that are listened for in the component using V-ON (native DOM events are not mounted in initEvents). Vue stores custom events declared in the parent component on the _parentListeners property of the child component (VM is the component instance of the child component, and _parentListeners are defined in initInternalComponent)

Enter updateComponentListeners, found the Vue will call the add function to register all of the custom events, and the add function for the component is called $on to monitor the effect of the custom event

//https://github.com/vuejs/vue/blob/dev/src/core/instance/events.js#L24
function add (event, fn) {
  target.$on(event, fn)
}

//https://github.com/vuejs/vue/blob/dev/src/core/vdom/helpers/update-listeners.js#L83
// Call add to register custom events.
add(event.name, cur, event.capture, event.passive, event.params)
Copy the code

beforeCreate

After adding custom events, go to initRender, define slot and createElement to render function, and change Vue $attrs and $Listeners into responsive properties

Then executes callHook (vm, ‘beforeCreate), literally see can guess the Vue will call at this time beforeCreate function, the life cycle in merge configuration items as mentioned before, life cycle function will eventually be wrapped into an array, So in fact Vue supports this as well

The callHook function will get the array of the life cycle functions for the given values in the $options property, and the beforeCreate function will get all the life cycle functions defined in the beforeCreate property. It then iterates sequentially and binds this context to each lifecycle function using the call method. Is that why lifecycle functions cannot be written using the clipper function

Initialize data

Then execute the initinjection, which is used to initialize the Inject API, but is not explained in detail because it is used less frequently in daily development (actually I am too bothered to research it).

InitState will then enter another key function, it will, in turn, initializes the props, the methods, data and computed, watch, we explain one by one

props

When components communicate with each other, the parent component passes parameters to the child component. The child component needs to define props to accept the properties transmitted by the parent component. However, according to Vue, the child component cannot modify the props transmitted by the parent component, because this violates a single data flow, which makes it very difficult to manage components. Vue will issue a warning

How does Vue know that the developer modified the properties of props? Again, the reason is to use the accessor descriptor setter

For those of you who are familiar with reactive principles, Vue turns the props object into a reactive object, and the fourth argument is a custom setter. This setter is triggered when the props object is modified, and a warning is generated when a single data stream is violated

methods

For methods, Vue defines warnings about irregularities during development, and then binds all Methods to VM instances so that we can get the current VM instance directly from this

data

By the key data, the data is generally preserved in current component needs to use the data, in addition to the root instance, component instance data is generally a function, because of JS, the characteristics of a reference type, if using objects, when there are multiple identical components, one of the components to modify the data data, will reflect to all components. When data returns an object as a function, a new object is generated each time it is executed, effectively solving this problem

Initializing data executes the initData function, internally executes the defined data function and takes the current instance as this, and assigns it to the _data internal attribute. It’s worth noting that you don’t get computed data during data execution. Because the data in computed is not initialized at this point

We then execute the proxy function, which maps the vm._data property to the VM property, acting as a proxy. This is used to write this[key] directly during development, again using the getter/setter. When we access this[key] we fire the getter, pointing directly to this._data[key], same with setter

One might ask, why not just write on the VM instance? Because we need to manage data on a unified object, the next step is to turn _data into a responsive object via Observe. In order to write more succinct during development, Vue takes this approach, which is very convenient

computed

After the initialization of computed data, Vue generates computed Watcher for each computed attribute. Only when the computed attribute’s dependency changes will IT notify Computed Watcher to update the computed attribute, so that real-time data can be updated without wasting performance. Also a great feature of Vue

watch

The $watch method is eventually called when the watch is initialized, generating a user Watcher that is immediately notified to perform a callback when the listening property changes

created

After initProvide is initialized, callHook(VM, ‘beforeCreate’) is executed. Just like beforeCreate, the created array defined on $options is iterated and the lifecycle function is executed

At this point, the entire component is created and can interact with the backend to retrieve data. However, the actual DOM node has not been rendered, and some of the operations that need to interact with the DOM cannot be performed in the Created hook, that is, the DOM of the generated view cannot be operated in the Created hook

Mount process

$options has an EL attribute. In vue-cli2, the CLI automatically passes an EL parameter to new Vue. In vue-cli3, it does not. Instead, the root instance is generated and $mount is actively called and passed to the mounted node. In fact, both are the same. You can also use $mount to manually mount components

Vue – cli2:

Vue-cli3:

$mount will eventually execute the mountComponent function

Having just escaped the _init tirade, jump into the mountComponent pit

Component mount I will not elaborate here, try to focus on the life cycle, interested friends can understand, or see my link below

beforeMount

When a component executes $mount and has mount points and rendering functions, the beforeMount hook is triggered to prepare the component for mounting

The function updateComponent that renders the view

Vue then defines an updateComponent function, which is the core of the mount and consists of two parts, the _render function and the _update function

  • The render function will finally execute before theinitRenderCreateElement function defined to create a VNode
  • The update function renders the vNode generated by the render function above into a real DOM tree and mounts it to the mount point

The first time updateComponent renders the entire DOM tree, the page is fully displayed

Render the watcher

A “render Watcher” is then instantiated, passed in updateComponent as a callback, and the updateComponet function is immediately executed internally

Rendering Watcher is simply a function that looks for changes in dependent variables in the template to determine whether a page needs to be refreshed. UpdateComponet is a function that updates the page, so it is passed in as a callback. For reactive variables in the template (variable A in the image below), the render Watcher is stored internally (because these variables can modify the view), the setter is triggered once the variable is modified, and the updateComponent function is executed again to refresh the view

mounted

When a component instance triggers mounted hooks, only the root instance will set to true. When does the component instance trigger mounted hooks?

SRC /core/vdom/create-component.js insert hook (component-specific vnode hook) and Vue declare an insertedVnodeQueue array that holds all component vNodes. Every time a component vNode is rendered as a DOM node, a vNode element is added to the array. When all components are rendered, mounted hooks are triggered in child => parent order (the innermost component’s mounted hooks are triggered first). And then we go back to _init, and finally trigger the mounted hook of the root instance. Why does that happen

At this point all the data has been initialized and the DOM nodes have been rendered. Component updates and component destruction are described

Component updates

Going back to the mountComponent diagram, when instantiating the render Watcher, Vue passes an object to the render Watcher that contains a before method. Executing the before method executes the beforeUpdate hook. When to execute this method?

Once the template’s dependent variable has changed, indicating that the view is about to change, the setter is triggered and the render Watcher callback, updateComponent, is executed to refresh the view. Before executing this callback, Vue looks to see if there is a before method, and if there is, it executes before first. Then execute updateCompont to refresh the view

Vue puts all watchers into a queue, flushSchedulerQueue iterates them in turn, and the rendering watcher has a before method that triggers the beforeUpdate hook

Then, when all the Watcher has been traversed and the data has been updated and the view has been refreshed, callUpdatedHooks are called to update the updated hook

Component destroyed

Component destruction starts with a view update. Vue determines the difference between the vNode that generated the new view and the vNode corresponding to the old view, and then removes the nodes in the view that do not need to be rendered. This process eventually calls the instance’s $destroy method. Corresponding to the source code of the SRC/core/instance/lifecycle. Js

To execute in order:

  1. The beforeDestory hook is executed directly first, indicating that the destruction of the node is ready to begin, which is the last time you can interact with the current component instance
  2. The parent of the current component is then found and removed from the children property of the parent
  3. Unregister the render Watcher (vm._watcher stores the render Watcher unique to each component)
  4. Log out of other Watcher (User Watcher,computed Watcher)
  5. Clear the DOM nodes rendered by this instance
  6. Implement Destroyed Hook
  7. Unlog all listener events ($off clears all listener events without passing the parameter)

conclusion

At this point, the entire Vue life cycle is over. Finally, what are the main things that are done in each life cycle

  • beforeCreate: merges the developer-defined configuration items with the internal configuration items of the Vue, initializes the component’s custom events, and defines the createElement function/initialization slot
  • createdInitialize inject, initialize all data (props -> methods -> data -> computed -> watch), and initialize provide
  • beforeMountWatcher: Looks for mounted nodes and is ready to start rendering the page/instantiate render according to the render function
  • mounted: The page is rendered
  • beforeUpdateRender the variable watcher depends on has changed and is ready to update the view
  • updated: Views and data are updated
  • beforeDestroy: Log out of watcher and delete the DOM node
  • destroyed: Logs out all listening events

In fact, in order to fully understand the Vue lifecycle, you need to understand other aspects, such as component mount, responsive principles, and maybe also understand the Vue compilation principles. Each of these points can be expanded into dozens of small points, but once you really understand the core principles of vue.js, I believe it is a big gain for personal growth.

We have a bright future ahead

The resources

Vue. Js technology revealed