By: Small Potato Blog park: www.cnblogs.com/HouJiao/ Nuggets: juejin.cn/user/243617…

Warm tips: This article is too long, one reading will be a little boring, I suggest you can first collect, divided into several times to read, so that better understanding.Copy the code

preface

I believe that many people, like me, will do the following series of summaries and learning at the beginning of understanding and learning Vue life cycle.

Conclusion 1

Vue instances are created through a series of initializations:

Set up data listening, compile templates, mount instances to the DOM, update the DOM as data changes, and so onCopy the code

Conclusion 2

During this initialization, several functions called “lifecycle hooks” are run:

BeforeCreate: before a component is created Created: a component is created. BeforeMount: before mounting a component is mounted. BeforeUpdate: Before updating a component updated: Before updating a component. Destroyed before component destruction: Component destruction is completedCopy the code

Example 1

Examples of the state of components in each hook function:

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Vue life cycle</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h3>{{info}}</h3>
        <button v-on:click='updateInfo'>Modify the data</button>
        <button v-on:click='destoryComponent'>Destruction of the component</button>
    </div>
    <script>
        var vm = new Vue({
            el: '#app'.data: {
                info: 'Life cycle of Vue'
            },
            beforeCreate: function(){
                console.log("BeforeCreated - before component creation");
                console.log("el:");
                console.log(this.$el);
                console.log("data:");
                console.log(this.$data);
            },
            created: function(){
                console.log("Created - Component created");
                console.log("el:");
                console.log(this.$el);
                console.log("data:");
                console.log(this.$data);
                console.log("info:");
                console.log(this.$data.info);
            },
            beforeMount: function(){
                console.log("BeforeMounted - Before components are mounted");
                console.log("el:");
                console.log(this.$el);
                console.log("data:");
                console.log(this.$data);
                console.log("info:");
                console.log(this.$data.info);
            },
            mounted: function(){
                console.log("Mounted - Component mounted complete");
                console.log("el:");
                console.log(this.$el);
                console.log("data:");
                console.log(this.$data);
                console.log("info:");
                console.log(this.$data.info);
            },
            beforeUpdate: function(){
                console.log("BeforeUpdate - component beforeUpdate");
                console.log("el:");
                console.log(this.$el);
                console.log("data:");
                console.log(this.$data);
                console.log("info:");
                console.log(this.$data.info);
            },
            updated: function(){
                console.log("Updated - Component updated");
                console.log("el:");
                console.log(this.$el);
                console.log("data:");
                console.log(this.$data);
                console.log("info:");
                console.log(this.$data.info);
            },
            beforeDestroy: function(){
                console.log("BeforeDestory - Before component destruction");

                // Try to modify data in data before component destruction
                this.info="Before component destruction";

                console.log("el:");
                console.log(this.$el);
                console.log("data:");
                console.log(this.$data);
                console.log("info:");
                console.log(this.$data.info);
            },
            destroyed: function(){
                console.log("Destoryed - Components destroyed");
                
                // Try to modify the data in data after the component is destroyed
                this.info="Component destroyed";

                console.log("el:");
                console.log(this.$el);
                console.log("data:");
                console.log(this.$data);
                console.log("info:");
                console.log(this.$data.info);
            },
            methods: {
                updateInfo: function(){
                    // Modify data
                    this.info = 'I've changed.'
                },
                destoryComponent: function(){
                    // Manually invoke the destruction component
                    this.$destroy(); }}});</script>
</body>
</html>
Copy the code

Conclusion 3:

The results of the previous example 1 run are summarized as follows.

Before component creation (beforeCreate)

Before the component is created, the DOM element EL that the component needs to mount and the component's data are not created.Copy the code
Component created

After creation, the component's data has been created, but the DOM element EL has not been created.Copy the code
Before mounting components (beforeMount):

The DOM element is created before the component is mounted, but the data in data has not yet been applied to the DOM element.Copy the code
Components are mounted.

After the component is mounted, the data in data has been successfully applied to the DOM element.Copy the code
Before component update (beforeUpdate)

Before the component is updated, the data data is updated, and the content of the DOM element mounted by the component is updated synchronously.Copy the code
Component updated

After the component is updated, the data data is updated and the content of the DOM element mounted by the component is updated synchronously. (Feels almost the same as beforeUpdate state)Copy the code
Before component destruction (beforeDestroy)

Before the component is destroyed, the component is no longer managed by vUE and we can continue to update the data, but the template is no longer updated.Copy the code
Component destroyed

After the component is destroyed, the component is no longer managed by vUE and we can continue to update the data, but the template is no longer updated.Copy the code

Component life cycle diagram

The final summary is the life cycle diagram from the Vue official website.

At this point, the initial learning of the Vue lifecycle is basically enough. Today, I will take you to understand the initialization phase of the Vue lifecycle from the Vue source code, and start the advanced learning of the Vue lifecycle.

The life cycle diagram on Vue’s official website is very critical and useful, and we will base our learning and summary on this diagram.

Creating a component instance

For a component, the first step the Vue framework takes is to create a Vue instance: new Vue(). What does new Vue() do? Let’s look at the source code implementation of the Vue constructor.

/ / note: the source position/vue/SRC/core/instance/index. Js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '.. /util/index'

function Vue (options) {
  if(process.env.NODE_ENV ! = ='production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

Copy the code

From the source code of the Vue constructor, you can see that there are two important things: the if conditional judgment logic and the _init method call. That below we carry on the silk to break cocoon on these two points, take a look at their source code implementation.

The index.js file is introduced before the new Vue code is executed, so initMixin, stateMixin, eventsMixin, lifecycleMixin, and renderMixin are executed before the new Vue. Internally, these methods basically define properties and instance methods for the component instance and assign initial values to the properties.

I won’t go into the details of the internal implementation of these methods, because this article is mainly to analyze the source code implementation of New Vue. So I want to give you a sense of the order of execution of the source code associated with this section, because the _init method that’s called in the Vue constructor has a lot of instance attribute access, assignment, and a lot of instance method calls, These instance properties and methods are defined by executing initMixin, stateMixin, eventsMixin, lifecycleMixin, and renderMixin when index.js is introduced.

Create component instance – if condition judgment logic

If condition judgment logic is as follows:

if(process.env.NODE_ENV ! = ='production' && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')}Copy the code

Let’s take a look at the logic of the first half of &&.

Process is a global variable built into the Node environment that provides information about and controls the current Node.js process. If you have the Node environment installed, you can enter this global variable directly from the command line.

This global variable contains a lot of information, and only some attributes have been cut off.

The env attribute of Process returns information about the current user environment. However, this information is not directly accessed to obtain the value, but needs to be set to obtain.

As you can see, I didn’t set this property, so I get undefined.

And then let’s seeVueIn the projectwebpackrightprocess.env.NODE_ENVDescription of the Settings:

NPM run dev sets process.env.node_mode to ‘development’ and NPM run build sets process.env.node_mode to ‘production’ This configuration is set in package.json scripts in the root directory of the Vue project

Therefore, the purpose of setting process.env.node_env is to distinguish whether the current Vue project is running in a development environment or a production environment. For different environments, webPack will be packaged with different plugins.

This instanceof Vue. This instanceof Vue.

I decided to explain this logic with an example, so it would be easy to understand.

Let’s write a function first.

function Person(name,age){
    this.name = name;
    this.age = age;
    this.printThis = function(){
        console.log(this);
    } 
    When a function is called, print this inside the function
    this.printThis();
}
Copy the code

JavaScript functions can be called in two ways: as normal functions and as constructors. Let’s call the Person function in both ways to see what this is inside the function.

// Call as a normal function
Person('Little potato biubiubiu'.18);
// create as a constructor
var pIns = new Person('Little potato biubiubiu');
Copy the code

This code is executed in the browser as follows:From the results, we can conclude:

Call Person as a normal function. The this object inside Person points to the browser's global window object and calls Person as a constructor. The this object inside Person points to the created instance objectCopy the code

So this is actually what this points to in JavaScript.

The result of this instanceof Fn logic inside a function Fn is true when called as a constructor.

The logic of the if condition is already clear:

If the Vue method is not called using new Vue in a non-production environment, there is a warning: Vue is a constructor and should be called with the 'new' keywordCopy the code

Creates a call to the component instance -_init method

The _init method is a method defined on the Vue prototype:

/ / note: the source position/vue/SRC/core/instance/init. Js
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options? :Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if(process.env.NODE_ENV ! = ='production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
Copy the code

Vue constructor in the source file path for the Vue/SRC/core/instance/index, js, one line of code in this file initMixin (Vue), after the method call will add _init method to the Vue prototype object. Js and new Vue are executed in the order I mentioned earlier.

So what’s going on in this _init method?

vm.$options

If you look at the code implementation inside _init, you can see that the first one sets a $options property for the component instance.

/ / note: the source position/vue/SRC/core/instance/init. Js
// merge options
if (options && options._isComponent) {
  // optimize internal component instantiation
  // since dynamic options merging is pretty slow, and none of the
  // internal component options needs special treatment.
  initInternalComponent(vm, options)
} else {
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  )
}
Copy the code

First of all,ifThe branchoptionsA variable isnew VueIs an option to pass.The meetifThe logic of branching is ifoptionsExists and is a component. In thenew Vue“Is clearly not satisfiedifBranch logic, so it will executeelseBranching logic.

The if branch logic is satisfied when components are created using the vue.extend method.

In the else branch, resolveConstructorOptions role is through a component instance constructor for current component option and the parent component option, through mergeOptions method will be merged the two options.

The parent component does not refer to the parent-child relationship generated by references between components, or the parent-child relationship associated with vue.extend. I don’t know much about vue.extend at this point, so I won’t go into it.

vm._renderProxy

Next, assign the component instance’s _renderProxy value.

/ / note: the source position/vue/SRC/core/instance/init. Js
/* istanbul ignore else */
    if(process.env.NODE_ENV ! = ='production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
Copy the code

In a non-production environment, the initProxy method is called to generate the VM’s proxy object _renderProxy. Otherwise, the value of _renderProxy is the instance of the current component. Then let’s look at how the initProxy method called in a non-production environment assigns values to vm._renderProxy.

/ / note: the source position/vue/SRC/core/instance/proxy. Js
const hasProxy = typeof Proxy! = ='undefined' && isNative(Proxy)
initProxy = function initProxy (vm) {
    if (hasProxy) {
      // determine which proxy handler to use
      const options = vm.$options
      const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
}
Copy the code

Inside the initProxy method, you actually wrap the component instance VM with the ES6 Proxy object and assign it to vm._renderProxy.

aboutProxyAs follows:So let’s just write a simple one aboutProxyExample usage of.

let obj = {
    'name': 'Little potato biubiubiu'.'age': 18
};
let handler = {
    get: function(target, property){
        if(target[property]){
            return target[property];
        }else{
            console.log(property + "Property does not exist and cannot be accessed");
            return null; }},set: function(target, property, value){
        if(target[property]){
            target[property] = value;
        }else{
            console.log(property + "Attribute does not exist, cannot be assigned.");
        }
    }
}
obj._renderProxy = null;
obj._renderProxy = new Proxy(obj, handler);
Copy the code

This method is modeled after the source code for vm Proxy writing method, we set the object obj Proxy.

According to the handler function implementation, when we access a property of the proxy object _renderProxy, if the property exists, we return the corresponding value directly. If the property does not exist, ‘property does not exist, cannot be accessed’ is printed and null is returned. When we modify an attribute of the proxy object _renderProxy, we assign a new value to the attribute if it exists; If not, print ‘Property does not exist, cannot be assigned’. We then run the above code in the browser console and access the properties of the proxy object:

Then modify the properties of the proxy object:

The results are consistent with our previous description. And then we’re back toinitProxyIt is actually visitingvmDo some validation, such as whether the attribute is on the VM, whether the accessed attribute name is valid, etc.

To sum up, this is essentially to make some errors in the code we write for our code in a non-production environment.

Multiple function calls in succession

The last thing you see is multiple functions being called consecutively.

initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
Copy the code

Let’s call the last few functions in order and sumVueOfficial website of theLife cycle diagramCompare:

You can see that the code basically corresponds to the diagram one to one, so the _init method is called the initialization method of the Vue instance. Let’s take a look at the sequential methods that are called inside _init.

InitLifecycle – Initializes the life cycle

/ / note: the source position/vue/SRC/core/instance/lifecycle. Js
export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if(parent && ! options.abstract) {while(parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) } vm.$parent = parent  vm.$root = parent ? parent.$root : vm vm.$children = [] vm.$refs = {} vm._watcher =null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}
Copy the code

In the initialization lifecycle function, the VM is the instance object of the current Vue component. We see that most of the inside of the function is assigning values to attributes of the vm instance object.

Properties starting with $are called component instance properties and are clearly explained on the Vue website.

The $parent property represents the parent of the current component, and you can see that in the while loop we recursively look for the first non-abstract parent: parent.$options.abstract && parent.$parent.

The parent component of non-abstract type is not well understood here, but can be directed in the comments section.

The $root property represents the parent of the current component. If the current component has a parent component, the root component of the current component inherits the parent component’s $root property. If the current component instance does not have a parent, the parent of the current component is itself.

The $children property represents the immediate children of the current component instance. Push (VM) adds the current component’s instance object to the parent component’s $children property. The current component is assigned the $children property of the parent component, and the children of the current component are responsible for adding $children to the data.

The $refs attribute represents the DOM element or component instance in the template where the ref attribute is registered.

InitEvents – Initializes events

/ / note: the source position/vue/SRC/core/instance/events. Js
export function initEvents (vm: Component) {
  // object.create (null): Creates an empty Object whose prototype is NULL
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}
Copy the code

vm._events

In the initialization event function, we first define a _events attribute to the VM and assign it an empty object. So what does _events represent? Let’s write some code to verify that.

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Vue life cycle</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var ChildComponent = Vue.component('child', {
            mounted() {
                console.log(this);
            },
            methods: {
                triggerSelf(){
                    console.log("triggerSelf");
                },
                triggerParent(){
                    this.$emit('updateinfo'); }},template: 
       

Child

triggerParent event

'
})
</script> </head> <body> <div id="app"> <h3>So here's the parent App</h3> <button v-on:click='destoryComponent'>Destruction of the component</button> <child v-on:updateinfo='updateInfo'> </child> </div> <script> var vm = new Vue({ el: '#app'.mounted() { console.log(this); }, methods: { updateInfo: function() {},destoryComponent: function(){},}});</script> </body> </html> Copy the code

Let’s take a look at the logic of this code.

The first is the Child component.

Create a component named Child component in which you declare two events using V-on. One event is triggerSelf, and the internal logic prints the string 'triggerSelf'. The other event is triggetParent, and the internal logic is to use $emit to trigger the parent component updateInfo event. We also print the value of component instance this in the component mounted hook function.Copy the code

Next comes the logic of the App components.

An event called destoryComponent is defined in the App component. The App component also references the Child component and binds a native DOM event called UpdateInfo to the child component. The App component's Mounted hook function also prints the value of component instance this.Copy the code

Because the Child component is referenced in the App component, the App component and the Child component form a parent-child relationship, and the App component is the parent and the child component is the child.

With the logic sorted out, we run the code and look at the printout of the _events property in both component instances.

As you can see from the print, the _events property of the current component instance holds only the events bound to the current component by the parent component, not all events in the component.

vm._hasHookEvent

The _hasHookEvent property indicates whether the parent component binds the hook function to the current component using the V-hook: hook function name.

updateComponentListeners(vm, listeners)

For this function, we need to focus first on the listeners parameter. Let’s see where it comes from.

// init parent attached events
const listeners = vm.$options._parentListeners
Copy the code

Initialize the event added by the parent component. The _events attribute holds events that are bound to the current component by the parent component. Parentlisteners are also events added by the parent component. What’s the difference between these two properties? Let’s modify the above example slightly by adding a print message (only the changes will be posted here).

<script>
// Modifies child mounted by printing its attributes
var ChildComponent = Vue.component('child', {
    mounted() {
        console.log("this._events:");
        console.log(this._events);
        console.log("this.$options._parentListeners:");
        console.log(this.$options._parentListeners); }})</script>

<! -- Modify the code that references the child component: add two event bindings (with event modifiers) -->
<child v-on:updateinfo='updateInfo'
       v-on:sayHello.once='sayHello'
       v-on:SayBye.capture='SayBye'>
</child>

<script>
// Add two methods sayHello and sayBye
var vm = new Vue({
    methods: {
        sayHello: function(){},SayBye: function(){},}});</script>
Copy the code

Then we run the code in the browser and see the results.

The _events and _parentListeners are actually events bound to the current component by the parent component. Only the saved keys are slightly different:

The event name key holds a concatenation of the string and the event name. This string is a conversion to the modifier (the.once modifier is converted to ~; The.capture modifier is converted to! The value corresponding to the event name is an array, which contains the corresponding event callback function. The vaule corresponding to the event name is the callback functionCopy the code

Ok, let’s continue our analysis.

The next step is to determine the process: If a process exists, execute updateComponentListeners. Let’s look at the internal implementation of this method.

/ / note: the source position/vue/SRC/core/instance/events. Js
export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}
Copy the code

You can see that inside this method you’re calling updateListeners again, so let’s take a look at the arguments to this function.

Notice: This parameter is an event added to the parent component.

OldListeners are old events, but the exact nature of these events is unclear. However, the oldListeners parameter value passed to the updateComponentListeners during the initialization of the event is null. So we’re not going to worry about that for now. A global search for updateComponentListeners under /vue/ SRC/will show that the function is called elsewhere, so it should be useful elsewhere.

Add: Add is a function. The internal logic code of the function is:

function add (event, fn) {
  target.$on(event, fn)
}
Copy the code

Remove: Remove is also a function. The internal logic code of the function is:

function remove (event, fn) {
  target.$off(event, fn)
}
Copy the code

CreateOnceHandler:

Vm: Needless to say, this parameter is the instance of the current component.

Here we focus on two important codes in the add and remove functions: target.$on and target.$off.

First, target is a global variable defined in the event.js file:

/ / note: the source position/vue/SRC/core/instance/events. Js
let target: any
Copy the code

Inside the updateComponentListeners function, we can see that the component instance is assigned to target:

/ / note: the source position/vue/SRC/core/instance/events. Js
target = vm
Copy the code

So target is the component instance. Of course, those familiar with Vue should quickly recognize that the $ON and $off methods themselves are event-related methods defined on component instances. In addition to the $ON and $off methods, there are two other methods on the component instance that relate to events: $once and $emit.

Here, we are not going to interpret the source code of the four event methods in detail, just a screenshot of the Vue official website for the use of the four instance method description.

vm.$on

vm.$once

vm.$emit

The use of VM. $emit is illustrated in detail in the Vue Parent-child Component Communication article.

vm.$off

updateListenersThe parameters of the function are basically explained, and then we go back toupdateListenersThe internal implementation of a function.

/ / note: the source position/vue/SRC/vdom/helpers/update - the listener. Js
export function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  createOnceHandler: Function,
  vm: Component
) {
  let name, def, cur, old, event
  // Loop events on the parent of the current component
  for (name in on) {
    // Get the event callback function based on the event name
    def = cur = on[name]  
    // The oldOn parameter is an oldnotice on the process of initialization, but is empty {}, so the value of old is undefined
    old = oldOn[name]     
    event = normalizeEvent(name)
   
    if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.once)) {
        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
      }
      // Add the parent event to the current component instance
      add(event.name, cur, event.capture, event.passive, event.params)
    }
  }
}
Copy the code

The first is the normalizeEvent function, which simply decomsnaps the event name. If the event name=’ updateInfo.once ‘, the event object returned by this function is:

{
    name: 'updateinfo'.once: true.capture: false.passive: false
}
Copy the code

The internal implementation of the normalizeEvent function is also very simple, so I’ll just sort out the conclusions here. Interested students can go to see the source code, source location: / vue/SRC/vdom/helpers/update – the listener. Js.

The next step is to do some if/else conditionals while looping the parent component’s events, adding the parent component’s events bound to the current component to the _events property of the current component instance. Or remove the corresponding event from the _events property of the current component instance.

The logic to add the events bound to the current component by the parent component to the current component’s _events property is implemented by calling vm.$on inside the add method. For details, see the source implementation of vm.$on. Also, from the implementation of the vm.$on function, you can see the correlation and difference between _events and _parentListener.

InitRender – Initializes the template

/ / note: the source position/vue/SRC/core/instance/render. Js
export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  
  // Bind createElement FN to the component instance
  vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if(process.env.NODE_ENV ! = ='production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () = > {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () = > {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)}else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null.true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null.true)}}Copy the code

The initRender function basically assigns values to attributes on the component instance VM: $slots, $scopeSlots, $createElement, $attrs, $Listeners.

The next step is to analyze these properties one by one to see the logic of what initRender is doing.

vm.$slots

This is from the official websitevm.$slotsFor convenience, I’ll write an example.

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Vue life cycle</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var ChildComponent = Vue.component('child', {
            mounted() {
                console.log("Clild components, enclosing $slots.");
                console.log(this.$slots);
            },
            template:'
       
Child
'
})
</script> </head> <body> <div id="app"> <h1 slot='root'>App component, slot = 'root'</h1> <child> <h3 slot='first'>Here is the slot = first</h3> <h3 slot='first'>Here is the slot = first</h3> <h3>There is no slot set here</h3> <h3 slot='last'>Here is the slot = last</h3> </child> </div> <script> var vm = new Vue({ el: '#app'.mounted() { console.log("App components, enclosing $slots."); console.log(this.$slots); }});</script> </body> </html> Copy the code

Run the code and see the result.As you can see,childThe component’svm.$slotsThe printed result is an object containing three key-value pairs. Among themkeyforfirstThe value of saves twoVNodeObject, these twoVnodeThe object is what we’re referring tochildComponentslot=firstOf the twoh3Elements. thekeyforlastSame thing with the value of alpha.

The value of key default holds four vNodes, one of which is the h3 element with no slot set when referencing the child component. The other three vNodes are actually newlines between the four H3 elements.

<child>
    <h3 slot='first'>Here is the slot = first</h3><h3 slot='first'>Here is the slot = first</h3><h3>There is no slot set here</h3><h3 slot='last'>Here is the slot = last</h3>
</child>
Copy the code

The value that eventually prints the key as default will only contain the H1 element for which slot is not set.

So the resolveSlots function in the source code resolves the slot elements passed by the parent component to the current component and converts them into the $slots object that Vnode assigns to the current component instance.

vm.$scopeSlots

Vm.$scopeSlots is the contents of the scope slot in Vue.

An empty object is temporarily assigned to vm.$scopeSlots here, which will be later assigned when the mount component calls vm.$mount.

vm.$createElement

Vm.$createElement is a function that takes two arguments:

The first argument: HTML element tag name The second argument: an array containing Vnode objectsCopy the code

$createElement compiles the Vnode elements in the Vnode object array into HTML nodes and places them in the HTML element specified by the first argument.

Vm.$slots stores the slot node that the parent component passes to the current component, and the slot holds an array of Vnode objects. So let’s use vm.

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Vue life cycle</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var ChildComponent = Vue.component('child', {
            render:function(){
                return this.$createElement('p'.this.$slots.first); }})</script>
    
</head>
<body>
    <div id="app">
        <h1 slot='root'>App component, slot = 'root'</h1>
        <child>
            <h3 slot='first'>Here is the slot = first</h3>
            <h3 slot='first'>Here is the slot = first</h3>
            <h3>There is no slot set here</h3>
            <h3 slot='last'>Here is the slot = last</h3>
        </child>
    </div>
    <script>
        var vm = new Vue({
            el: '#app'
        });
    </script>
</body>
</html>
Copy the code

This sample code is similar to the previous vm.$slots code, which writes the render function when creating the child component and uses vm.$createElement to return the contents of the template. So our results in the browser.

As you can see, as we said, vm.$createElement compiles the frist array of two Vnode objects from $slots into two H3 elements. Put it into the p element specified in the first argument, process the return value of vm.$createElement through the render function of the child component, and you see what the browser shows.

$createElement (Vue, Vue, Vnode, Vnode, Vue, Vnode)

vm.
a t t r and v m . Attr and vm.
listener

These two properties are instance properties related to component communication and are assigned in a very simple way.

CallHook (beforeCreate)- Calls the lifecycle hook function

The purpose of the callhook function is to call Vue’s lifecycle hook function. The second argument to the callhook function is a string specifying which hook function to call. The beforeCreate hook function will be called during initialization after initLifecycle, initState, and initRender are executed in sequence.

Next, look at the source code implementation.

/ / note: the source position/vue/SRC/core/instance/lifecycle. Js
export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  // Get the component's hook function from the component instance based on its name
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}
Copy the code

You first get the component’s hook function from the component instance by its name, then invoke invokeWithErrorHandling, The third argument to invokeWithErrorHandling is null, so inside invokeWithErrorHandling is the apply method that invokes the hook function.

We should see in the source code that we have loop Handlers and then we call the invokeWithErrorHandling function. In fact, we can write multiple hooks with the same name when we write components, but in fact Vue only keeps the last hook function with the same name on the instance. So what’s the point of this loop?

To find out, I wasbeforeCratedIt’s printed in this hookthis.$options['before']“, and discovers that the result is an array with only one element.So if you think about it, you can see how this loop is written.

InitInjections – Initialize injection

InitInjections This function is inject dependent in Vue. So let’s take a lookThe official document explains Inject.

Inject and provide are commonly used together and actually communicate between parent and child components, but are recommended for use when developing higher-order components.

Provide is what initProvide is below.

aboutinjectandprovideThe usage of “is characterized as long as the parent component is usedprovideA data is registered, and no matter how deep the child component is nested, the child component can pass throughinjectGets the data registered on the parent component.A rough ideainjectandprovideAfter the use of, can guessinitInjectionsWhat happens inside the functioninjectParse to retrieve the current componentinjectYou need to find the value in the parent componentprovideIs a value registered in the. If so, return the value. If not, continue to look up the parent component. So let’s seeinitInjectionsFunction source code implementation.

/ / note: the source position/vue/SRC/core/instance/inject. Js
export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key= > {
      /* istanbul ignore else */
      if(process.env.NODE_ENV ! = ='production') {
        defineReactive(vm, key, result[key], () = > {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)}}Copy the code

The first line of the source code calls the resolveInject function and passes the inject configuration and component instance of the current component. This function is called recursively to find the parent component’s provide, and its core code is as follows:

// source is the current component instance
let source = vm
while (source) {
    if (source._provided && hasOwn(source._provided, provideKey)) {
      result[key] = source._provided[provideKey]
      break
    }
    // Continue to look up the parent component
    source = source.$parent
  }
Copy the code

If the value is true, the parent component contains data registered with provide. If the value is true, the parent component contains data registered with provide. Then we need to further determine whether the data registered by the parent component provide exists in the inject property of the current component.

In the recursive search process, the resolveInject function will put the value corresponding to the element in the inject into a dictionary and return it as the return value.

Inject: [‘name’,’age’,’height’]. ResolveInject: [‘name’,’age’,’height’]

{
    'name': 'Little potato biubiubiu'.'age': 18.'height': '180'
}
Copy the code

Finally, back to the initinjection function, the following code is to make the inject data responsive in non-production environments, using the same principle of bidirectional data binding.

InitState – Initialization state

/ / note: the source position/vue/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

Initialize state This function primarily initializes properties defined by the Vue component: props, methods, data, computed, and Watch.

Let’s focus on data initialization, the implementation of the initData function.

/ / note: the source position/vue/SRC/core/instance/state. Js
function initData (vm: Component) {
  let data = vm.$options.data
  
  // Omit some code
  
  // observe data
  observe(data, true /* asRootData */)}Copy the code

Inside the initData function we see a familiar line of code :observe(data). The data parameter is the data data defined in the Vue component. As the comment says, all this code does is make the object observable.

By tracing back to the observe function, you can trace the previous implementation and invocation of the Observer in vue2.

So now we know that making an object observable is done in the initData step of the Vue instance initialization phase.

InitProvide – initialization

/ / note: the source position/vue/SRC/core/instance/inject. Js
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}
Copy the code

This function is the provide we mentioned when summing up the initInjections function. This function is also very simple, setting _provide for the current component instance.

CallHook (created)- Calls lifecycle hook functions

InitLifecycle, initState, initRender, callhook(‘beforeCreate’), initInject, initProvide, etc. The Created hook function is then called.

The internal implementation of callHook was mentioned earlier and is the same here, so I won’t repeat it.

conclusion

At this point, the initialization phase of the lifecycle of vue 2.x is completed. Here is a brief summary of the initialization phase.

Source code is still very powerful, the learning process is still more difficult and boring, but will find a lot of interesting writing, and we often read some of the theoretical content in the source code of real practice, so must stick to it. Look forward to the next article [Don’t you know the Vue life cycle yet? Takes you through the Vue source code to understand the ve2. X life cycle (template compilation phase)].

Write in the last

If this article has helped you, please follow ❤️ + like ❤️ and encourage the author

Article public number first, focus on unknown treasure program yuan for the first time to get the latest article

Ink ❤ ️ ~