Vue source analysis [2] – New Vue process

The following code and analysis process need to be viewed in conjunction with vue.js source code, by comparing the break points one by one.

Template code

<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src=". /.. /.. /oldVue.js"></script>
</head>

<body>
    <div id="app">
        <h2>Start saving</h2>
        <div>Monthly deposit :¥{{money}}</div>
        <div>Storage :{{num}} months</div>
        <div>¥{{total}}</div>
        <button @click="getMoreMoney">{{arryList[0].name}} Save more</button>
        <msg-tip :msginfo='msgText' :totalnum='total'></msg-tip>
    </div>

    <script>
        debugger;
        // Define a new component
        var a =  {
            props: ['msginfo'.'totalnum'].data: function () {
                return {
                    count: 0}},template: {{totalNum}}
        }

        var app = new Vue({
            el: '#app'.components: { msgTip: a},
            beforeCreate(){},created(){},beforeMount(){},mounted: () = >{},beforeUpdate(){},updated(){},beforeDestroy(){},destroyed(){},data: function () {
                return {
                    money: 100.num: 12.arryList: [{name:'subtrees'}].msgText: "Excellent Naco:"}},computed: {
                total() {
                    return this.money * this.num; }},methods: {
                getMoreMoney() {
                    this.money = this.money * 2
                    this.arryList.unshift({name: 'trees'}}}})</script>

</body>

</html>
Copy the code

1. Introduction

The structure of this paper is based on points, lines and planes.

  • The point is the function
  • The line is the execution flow of the function
  • Surface is a detailed interpretation of the source code

It is not recommended to read the source code directly, many functions are very long, and the link is very long, in the absence of a general understanding of the function, probability, you will find after reading the source code, WC I just read the source code? But I don’t remember what they did. Therefore, first look at the role, then look at the process, and then expand to see the source code.


2. Overall process

3. Trigger new Vue

Execute new Vue in HTML js, which goes to the Vue constructor in vue.js.

The options argument is an input parameter to the new Vue in HTML.

Source:

// trigger in HTML
var app = newVue({... })// The vue constructor in vue.js
function Vue(options) {
        // In the webpack version of vue here is: development is process.env.node_env
        if ("development"! = ='production' && !(this instanceof Vue)
        ) {
            warn('Vue is a constructor and should be called with the `new` keyword');
        }
        this._init(options);
}
Copy the code

Let’s look at what the input parameter of options is:

{
    beforeCreate: ƒ beforeCreate (),beforeDestroy: ƒ beforeDestroy (),beforeMount: ƒ beforeMount (),beforeUpdate: ƒ beforeUpdate (),components: {  / / component
        msgTip: { / / component name
            data: ƒ (),
            props: [
                msginfo,
                totalnum
            ],
            template: {{totalNum}}}},computed: {total: ƒ}
    created: ƒ created ()data: ƒ ()
    destroyed: ƒ destroyed ()el: "#app"
    methods: {getMoreMoney: ƒ}
    mounted: () = >{}updated: ƒ updated ()__proto__: Object
}
Copy the code

4. this._init

Function:

  1. Initialize the VM.$options (merge options)
  2. A bunch of initialization work (including initialization lifecycle, events, render functions, data, Prop, Methods, Computed, Watch, and so on)
  3. Mount Life hooks to mount elements (Compiler is used to generate renderers in full builds)

Source:

// When it executes, it goes vue.prototype. _init
this._init(options);

var uid$3 = 0;

// Initialize vue
function initMixin(Vue) {
        Vue.prototype._init = function (options) {
            var vm = this;
            // Increment each component with a unique UID
            // uid$3 itself is incremented by 1, but the expression is not, so vm._uid is initialized to 0
            vm._uid = uid$3+ +;// Start, end tags
            var startTag, endTag;
            // Monitor browser performance
            // 1 config
            if ("development"! = ='production' && config.performance && mark) {
                startTag = "vue-perf-start:" + (vm._uid);
                endTag = "vue-perf-end:" + (vm._uid);
                mark(startTag);
            }
            // A sign to avoid being observed
            vm._isVue = true;
            // If the current file is a component, perform subcomponent initialization
            if (options && options._isComponent) { 
            // Put the attributes of the new Vue parameter options into vm.$options
                initInternalComponent(vm, options);
            } else {
                /** * this section is all about merging the options, and then returning the merged options ** Initializing the root component, merge the global configuration of the Vue to the local configuration of the root component, For example, global components registered by * Vue.component are merged into the root instance's components option */
                // Merge argument merges parent and child objects together
                vm.$options = mergeOptions(
                    // Parse the options attribute on constructor
                    resolveConstructorOptions(vm.constructor), 
                    options || {},
                    vm 
                );
            }
            {
                // Initialize the proxy listener
                initProxy(vm);
            }
            // Execute a series of hook functions, etc
            vm._self = vm;
            // Initialize component instance relationship properties, such as $parent, $children, $root, $refs, etc
            initLifecycle(vm);
              /** * initializes the custom event. There is a bit of caution here, so we use <comp@click="handleClick" /> The listener is not the parent component, but the child component itself
            initEvents(vm); 
            $slot = vm.$slot = vm.$createElement = h
            initRender(vm); 
            callHook(vm, 'beforeCreate'); 
            // Initialize the inject configuration item of the component to obtain the configuration object in the form of result[key] = val
            // The resulting data is then processed responsively and each key is proxyed to the VM instance
            initInjections(vm); 
            initState(vm);  // Focus on data responsiveness, processing props, methods, data, computed, and watch
            The provide option should be an object or a function that returns an object. This object contains properties that can be injected into its descendants for communication between components.
            initProvide(vm); 
            callHook(vm, 'created'); 

            // Browser performance monitoring
            if ("development"! = ='production' && config.performance && mark) {
                vm._name = formatComponentName(vm, false);
                mark(endTag);
                measure(("vue " + (vm._name) + " init"), startTag, endTag);
            }

           // If the configuration item is found to have an EL option, the $mount method is automatically called
           If there is no EL, $mount must be called manually
            if(vm.$options.el) { vm.$mount(vm.$options.el); }}; }Copy the code

Note 1: config

Config is also a set of configuration objects defined before new Vue:


4-1. resolveConstructorOptions

Function:

Parse the configuration object Options from the component constructor and merge the base class options

Execution process:

  • Only executed if there is super on the constructor
  • ResolveConstructorOptions recursive implementation, introduced to the constructor of super and returns the new options
  • Reset constructor superOptions to recursively acquired options when constructor superOptions is not equal to recursively acquired options
  • If options are modified, merge the extendOptions constructor with the modified item
  • Merge the acquired options and the merged extendOptions again
  • Returns the modified options

Source:

 function resolveConstructorOptions(
        Ctor // vm.constructor is the constructor itself
        ) {
        debugger
        /** * new Vue before initGlobalAPI, Vue. Options becomes * {* components: {KeepAlive: {... }, the Transition: {... }, TransitionGroup: {... }}, directives: {model: {... }, show: {... }}, filters: {}, _base: ƒ Vue(options)} */
        var options = Ctor.options;
        // Ctor has the super attribute, indicating that Ctor is a subclass inherited from the subclass built by vue. extend
        if (Ctor.super) { / / the base class
            // Call base class to inherit base class, return options
            var superOptions = resolveConstructorOptions(Ctor.super);  // Take vue. options and refer to the objects shown above
            var cachedSuperOptions = Ctor.superOptions; // Take vue. options and refer to the objects shown above
            if(superOptions ! == cachedSuperOptions) {// Determine if options in the base class are not equal to options in the subclass
                // The base class constructor option has been changed and needs to be reset
                Ctor.superOptions = superOptions; // assign his base class options to ctor.superoptions
                // Check if there are any late changes/additional options (#4976)
                var modifiedOptions = resolveModifiedOptions(Ctor);
                // If there are options that have been modified or added, merge the two options
                if (modifiedOptions) {
                    //extendOptions merges extended parameters
                    extend(Ctor.extendOptions, modifiedOptions);
                }
                // Take Ctor. ExtendOptions to combine two objects into one object
                options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
                if(options.name) { options.components[options.name] = Ctor; }}}return options 
        /** Returns parameter format * {components: {}, data: ƒ (), cache: {}, filters: {}, template: "< div > detail message is: {{MSG}} < / div >", _Ctor: {0: ƒ}, _base: ƒ Vue (options), __proto__ : Object} * /
    }
Copy the code

4-1-1. mergeOptions

Function:

Combining two objects into one object combines the parent and child objects together, and the same key takes precedence over the child objects

Execution process:

  • Specifies the name of the child component, warning if it does not conform
  • Format Props, Inject, and Direcitives in the child into the corresponding canonical format, respectively
  • If child has extends, then the extends and parent recurse to get the combined parent
  • If child is a mixins (array), recurse each item of the mixins and parent to get the merged parent
  • By father option or the option key, son to obtain corresponding merge method in strats, merge father option and the option of the corresponding attribute values, then merge the value of the corresponding key to assign a value to the options.
  • Returns merged options (final merged item of parent and child options)

Source:

    function mergeOptions(
        parent, // example: {components: {... }, directives: {... }, filters: {... }, _base: ƒ}
        child, // Parameters to new Vue, for example: {el: "#app", beforeCreate: ƒ,... }
        vm  / / the Vue instance
    ) {{// Verify child components
            checkComponents(child);
        }
        if (typeof child === 'function') {
            child = child.options;
        }
        // specify Props, Inject, Direcitives in child

        // Specification property to ensure that all props are object - based specifications
        normalizeProps(child, vm);

        // convert an array to an object such as [1,2,3] to
        normalizeInject(child, vm);

        // * normalizeDirectives obtain the value of the directive object. Dirs [key] = {bind: def, update: def}
        normalizeDirectives(child);

        /** * Recurses the mergeOptions method to see if child has extends (extends). Parent is the current parent, and child is the extends value of the current child. Override parent */ after the call
        var extendsFrom = child.extends;
        if (extendsFrom) {
            / / such as recursion
            parent = mergeOptions(parent, extendsFrom, vm);
        }

        /** * Test child for mixins, recurse to the current mergeOptions method, and override the latest result of the last mergeOptions call to parent; * /
        if (child.mixins) {
            for (var i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm); }}// Update merged options
        // Parent and child combine the fields to form new options
        // Select the strats method from the parent key and set the value as the value of the options key
        var options = {};
        var key;
        for (key in parent) {
            mergeField(key);
        }
        for (key in child) {
            if (!hasOwn(parent, key)) {
                mergeField(key);
            }
        }
        /** * Strats has methods el, propsData, data, provide, watch, props, methods, Inject, computed, components, Directives, filters. * * Merge the strats merge method using the parent or child key, then merge the check items and the child attribute values, and reassign the merged value to the options key. * /
        function mergeField(key) {
            var strat = strats[key] || defaultStrat;
            options[key] = strat(parent[key], child[key], vm, key);
        }
        // In recursion, options is returned as the parent parameter of the next function
        return options
    }
Copy the code
4-1-1-1. checkComponents

Function:

Verify that our component name character does not conform to the specification.

The authentication component name can contain only alphanumeric characters and hyphens and must start with a letter.

The component name is not a built-in slot, Component, or HTML native tag or SVG tag

The source code

    function checkComponents(options) {
        for (var key in options.components) {
            validateComponentName(key); // Pass in the component name}}function validateComponentName(name) {
        if (!/^[a-zA-Z][\w-]*$/.test(name)) {
            // The component name can contain only alphanumeric characters and hyphens, and must start with a letter.
            warn(
                'Invalid component name: "' + name + '". Component names ' +
                'can only contain alphanumeric characters and the hyphen, ' +
                'and must start with a letter.'
            );
        }
        // Whether built-in tags slot, Component, HTML native tags or SVG tags
        if (isBuiltInTag(name) || config.isReservedTag(name)) {
            // Do not use built-in or reserved HTML elements as component names
            warn(
                'Do not use built-in or reserved HTML elements as component ' +
                'id: '+ name ); }}// Check whether the tag is built-in.
    var isBuiltInTag = makeMap('slot,component'.true);
    
    // Preserve tags to determine whether they are NATIVE HTML tags or SVG tags
    var isReservedTag = function (tag) {
        return isHTMLTag(tag) || isSVG(tag)
    };
Copy the code
4-1-1-2. normalizeProps

Function:

This function deals with the props of the child component

Given the restriction: props supports only objects and arrays

Format the props of the object, and then re-assign to the props property of the child component

Execution process:

  • Options are the configuration items of the child component passed in, for example:{data: ƒ (), props: [" msginfo "], (2) the template: "< div > {{msginfo}}"}.
  • Exit if options does not have props
  • If the props is an array, then the props received by the child component is separated from theprops:['myName', 'detail-id']The format becomes:props: {myName: {type: null},detailId: {type: null}}
  • If the props object, the props received by the child component is also processed into a unified format
  • Finally, normalize all props inside the child, overriding the props property of the child component

Source:

    function normalizeProps(
        options, / / the child component configuration, example: data: ƒ (), props: [" msginfo "], (2) the template: "< div > {{msginfo}}"
        vm / / the Vue instance
        ) {
            debugger
        // If there is no props in the child (write only if the child needs to communicate with the parent), exit
        var props = options.props;
        if(! props) {return
        }
        var res = {};
        var i, val, name;
        /** * Example: Change the props received by the subcomponent from props:['myName', 'detail-id'] to: * props: {myName: {type: null}, detailId: {type: null } } */
        if (Array.isArray(props)) {
            i = props.length;
            while (i--) {
                val = props[i];
                if (typeof val === 'string') {
                    /* Change the name format of "detail-id" to "detailId */"
                    name = camelize(val);

                    res[name] = { type: null };
                } else {
                    // Props is an array, but the array entry is not a string, warning: "When using array syntax, props must be a string
                    warn('props must be strings when using array syntax.'); }}}else if (isPlainObject(props)) {
        /** * Example: {* myName: String, portList: {type: Array, required: true, default: ()=>[]},} after formatting, becomes: props: {myName: {type: String}, portList: {type: Array, Required: true, default: ()=>[]},} */
            for (var key in props) {
                val = props[key];
                name = camelize(key);
                res[name] = isPlainObject(val)
                    ? val
                    : { type: val }; }}else {
            // If it is not an object or array, the props only supports objects and arrays
            warn(
                "Invalid value for option \"props\": expected an Array or an Object, " +
                "but got " + (toRawType(props)) + ".",
                vm
            );
        }
        // Normalize all props inside the child, overwriting the props attribute of the source child
        options.props = res;
    }
Copy the code
4-1-1-3. normalizeInject

Function:

Make sure all inject option syntax is normalized to the object format. Check the Inject data type. We know that Vue can receive Inject as an array or as an object

Execution process:

  • Options are the configuration items of the child component passed in, for example:{data: ƒ (), props: [" msginfo "], (2) the template: "< div > {{msginfo}}"}.
  • If options has no inject item, exit
  • Inject only supports objects and arrays
  • And the uniform format processing of inject, and then re-assign value to the sub-component of inject attribute

Source:

    function normalizeInject(
        options, / / the child component configuration, example: data: ƒ (), props: [" msginfo "], (2) the template: "< div > {{msginfo}}"
        vm / / the Vue instance
    ) {
        // Provide and inject mainly provide use cases for high-level plug-in/component libraries. Direct use in application code is not recommended.
        /** * This pair of options needs to be used together to allow an ancestor component to inject a dependency into all of its descendants, * regardless of how deep the component hierarchy is, and remain in effect as long as the upstream/downstream relationship is established. * /
        var inject = options.inject;
        if(! inject) {return
        }
        // If there is inject, empty it
        // Save the formatted inject
        var normalized = options.inject = {};
        /** * array format processing */
        if (Array.isArray(inject)) {
            for (var i = 0; i < inject.length; i++) {
                /* Convert arrays into objects. For example, inject: ['foo','bar']. The conversion process is: * normalized['foo']={from: 'foo'} * normalized['bar']={from: Results: 'bar'} {foo: {the from: 'foo'}, bar: {the from: 'bar'}} * /
                normalized[inject[i]] = { from: inject[i] }; }}/** * object format processing */
        else if (isPlainObject(inject)) {
            for (var key in inject) {
                /** If inject is: {foo: {from: 'name1',default: 'name1'}, bar} * The conversion process is: first traversal, key is foo, val is {from: 'name1',default: {foo: {from: 'name1'}, default: 'name1'}, extend {from: 'foo'} and {from: 'name1',default: 'name1'} 'name1' }, bar:{from: bar} } */
                var val = inject[key];
                normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val }; }}else {
            warn(
                "Invalid value for option \"inject\": expected an Array or an Object, " +
                "but got " + (toRawType(inject)) + ".", vm ); }}Copy the code
4-1-1-4. normalizeDirectives

Function:

Normalize the original function instructions into object format. The value of the loop object instruction if it is a function conversion.

Such as:

Options. Directives for:

{ getList: a(), delete: b()}
Copy the code

To:

{ 
   getList: { bind: a(), update: a() },
   delete: { bind: b(), update: b() }
}
Copy the code

Execution process:

  • If the instruction exists
  • Iterate over an instruction array, converting it only if the iterated item is a function

Source:

  function normalizeDirectives(
        options // The input parameter is child
    ) {
        debugger
        // Get the instructions in the argument
        var dirs = options.directives;
        if (dirs) { // If the instruction exists
            for (var key in dirs) {
                var def = dirs[key];  // Get the value of the instruction
                if (typeof def === 'function') { // if it is a function
                    // Add an object and a value to the function
                    /** For example: options. cache {getList: a(), delete: b()}, convert to {getList: {bind: a(), update: a()}, delete: {bind: b(), update: b() } } */
                    dirs[key] = { bind: def, update: def }; }}}}Copy the code
4-1-1-5. mergeField

Function:

Merge and update options.

The merge method in Strats is obtained using the parent or child key.

Then, merge the parent and child options and reassign the combined value to the options.

Source:

        function mergeField(key) {
            var strat = strats[key] || defaultStrat;
            options[key] = strat(parent[key], child[key], vm, key);
        }
Copy the code

Strats is defined before New Vue. It can be said that it contains everything that a Vue needs, such as life cycle, data,watch, EL, and a series of merge functions.


4-2. initProxy

Proxy is quite important, and we will write a separate article on proxy later

Function:

If the Proxy attribute exists, the wrapped VM attribute is assigned to the _renderProxy attribute value; otherwise, the VM is the instance itself.

Execution process:

  • Determine whether the built-in functions of the system have es6 Proxy object API. If no, assign vm directly to vm._renderProxy
  • If so, go ahead and select the agent handler based on whether render exists in vm.$options

Source:

// Initialize the proxy listener
initProxy = function initProxy(vm) {
            // Check whether built-in system functions have es6 Proxy object API
            // var hasProxy = typeof Proxy ! == 'undefined' && isNative(Proxy);
            if (hasProxy) {
                // Determine which agent handler to use
                var options = vm.$options;
                var handlers = options.render && options.render._withStripped
                    ? getHandler  / / get the value
                    : hasHandler;  // Determine internal functions so that templates in VUE can use built-in functions
                // Instantiate the proxy object, just add the warning log here
                vm._renderProxy = new Proxy(vm, handlers);
            } else {
                // If the proxy cannot assign directlyvm._renderProxy = vm; }};/** * The hasHandler method is used to see if a VM instance has an attribute - for example, the hasHandler method */ is triggered when a for in loop is called to iterate over vm instance attributes

var hasHandler = {
            The has method returns the value of a Boolean property.
            has: function has(target, key) {
                debugger
                var has = key in target;
                // Whether there is a global API is the window's built-in function
                / / global API
                // var allowedGlobals = makeMap(
                // 'Infinity,undefined,NaN,isFinite,isNaN,' +
                // 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
                // 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
                // 'require' // for Webpack/Browserify
                // );

                var isAllowed = allowedGlobals(key) || key.charAt(0) = = ='_';
                // Issue a warning if the key does not exist in the target object and is not (global API or if the first character is not _)
                // Determine whether the attribute name is available
                if(! has && ! isAllowed) { warnNonPresent(target, key); }/ / return true
                returnhas || ! isAllowed } };// This method can provide an alert if the developer incorrectly calls vm properties.
var getHandler = {
            // The catcher for the property read operation.
            get: function get(target, key) {
                // warning if key is a string and key is not in target
                if (typeof key === 'string' && !(key in target)) {
                    warnNonPresent(target, key);
                }
                // Returns the target value
                return target[key]
            }
};
        
Copy the code

4-3. initLifecycle

Function:

Initialize lifecycle specific attributes of the VM instance, such as:

  • mount$parent, can be accessed throughthis.$parentTo access the parent component directly
  • mount$refs, can be accessed throughthis.$refsTo directly access all of the child components of the ref that have been registered
  • And other attributes

Source:

initLifecycle(vm)

function initLifecycle(vm) {
        debugger
        var options = vm.$options;
        // Parent instance of the child component
        var parent = options.parent;
        // When the parent instance exists and it is not an abstract component
        if(parent && ! options.abstract) {// Check whether the parent node exists, and check whether the abstract node exists
            while (parent.$options.abstract && parent.$parent) {
                // Find the top-level parent
                parent = parent.$parent;
            }
            // Add a VM to the child node, and show off the current instance in the parent instance
            parent.$children.push(vm);
        }
        // Specify the parent instance of the created instance to establish a parent-child relationship between the two. Child instances can access parent instances with this.$parent
        vm.$parent = parent; 
        // The root Vue instance of the current component tree. If the current instance has no parent, the instance will be itself.
        vm.$root = parent ? parent.$root : vm; 
        vm.$children = []; // A direct child of the current instance. Note that $children does not guarantee order and is not responsive.
        vm.$refs = {}; // An object that holds all the children of the ref registered.
        vm._watcher = null; // The component instance corresponds to the Watcher instance object.
        vm._inactive = null; // Indicates the status of the keep-alive component. If activated, the value is false; otherwise, it is true.
        vm._directInactive = false;  // The inactive disabled component flag is also a property that indicates the state of the component in keep-alive.
        vm._isMounted = false; // Whether the current instance is mounted (mounted in the life cycle diagram).
        vm._isDestroyed = false; // Whether the current instance has been destroyed (corresponding to destroyed in the lifecycle diagram).
        // Whether the current instance is being destroyed, which has not been completed (between deforeDestroy and Destroyed in the lifecycle diagram).
        vm._isBeingDestroyed = false; 
}
Copy the code

4-4. initEvents

Function:

Component event updates are executed only when vm.$options._parentListeners (events bound to the current component by the parent component) exist.

Update data sources and add functions for new values and delete functions for old values

Source:

initEvents(vm)

function initEvents(vm) {
        debugger
        // The event that the parent component binds to the current component
        vm._events = Object.create(null);
        vm._hasHookEvent = false;
        // The event that the parent component binds to the current component
        var listeners = vm.$options._parentListeners;
        if (listeners) {
            // Update component eventsupdateComponentListeners(vm, listeners); }}function updateComponentListeners(
        vm,  // Vue instance object
        listeners,  // The event object that the parent component binds to the current component
        oldListeners // The old event object on the current component
    ) {
        debugger
        target = vm;
        // Update the data source and add functions for new values and delete functions for old values
        updateListeners(listeners, oldListeners || {}, add, remove$1, vm);
        target = undefined;
}
Copy the code

This is where we end up with the updateListeners function, which is explained in article 1.

4-5. initRender

Function:

The main functions of this function are:

1, get the _parentVnode object (child component exists)

2. Resolve vNodes into slot objects

3, Mount ‘_c’ function to vnode

Call defineReactive (explained in the responsive section) to intercept the ‘attrs’ and ‘attrs’ and ‘listeners’ attributes of the instance

Source:

    function initRender(vm) {
        vm._vnode = null; // Last vonde
        vm._staticTrees = null; // The v-once cache tree
        var options = vm.$options; // Get parameters
        var parentVnode = vm.$vnode = options._parentVnode; // Placeholder nodes in the parent tree
        var renderContext = parentVnode && parentVnode.context; // this context
        // Execute resolveSlots to obtain the slots information under the placeholder VNode
        // 
        debugger
        /** * Execute resolveSlots to get the slots information under the VNode placeholder, such as the div node here. RenderContext is a Vue instance, and options._renderChildren is a slot array for vNodes. * such as: * [{{tag: "div", data: {attrs: {...}, slot: "you"}...}, {tag: undefined, data: undefined...}, {tag: "Div", data: {attrs: {...}, slot: "my"}...}, {tag: undefined, data: undefined...}}] implement vm. After $slots format for: {you: [VNode], my: [VNode], default: [VNode, VNode] } */
        vm.$slots = resolveSlots(options._renderChildren, renderContext);
        vm.$scopedSlots = emptyObject;
        // Bind createElement fn to this instance
        // Then we get the proper render context.
        // Internal versions are used by template-compiled rendering functions
        // Create a data structure for the virtual DOM
        vm._c = function (a, b, c, d) {
            return createElement(
                vm, //vm new Vue instantiated object
                a, // Can be vonde or command
                b,
                c,
                d,
                false
            );
        };
        // User-written rendering.
        vm.$createElement = function (a, b, c, d) {
            return createElement(vm, a, b, c, d, true);
        };
        // $attrs and $Listener will be exposed to make temporary creation easier.
        var parentData = parentVnode && parentVnode.data;
        {
            // Define a responsive property on object. This method converts the property key of object obj into a getter/setter
            Reactive properties collect dependencies in getters and fire dependencies in setters.

            // We will examine it in the reactive section
            defineReactive(
                vm,
                '$attrs',
                parentData && parentData.attrs || emptyObject,
                function () {
                    !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
                },
                true
            );
            Subscribers are notified of new value changes by defineProperty's set method
            defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () {
                !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
            }, true); }}Copy the code

4-5-1. resolveSlots

The following analysis is based on the following example.

The parent component

<msg-tip :msginfo='msgText' : totalNum ='total'> <div slot='you'> you </div> <div slot='my'> I </div> him </msg-tip>Copy the code

Child components

The < div > {{msginfo}} saved ${{totalnum}} < slot name = 'you' > < / slot > < slot name = 'my' > < / slot > < slot > < / slot > < / div >Copy the code

Function:

[This function is the core of slot slots]

Collect all slots in the parent component, classify named and unnamed slots, and place the corresponding Vnode under the corresponding slot, returning a wrapper object.

Execution process:

  • Pass in the set of vNodes corresponding to slot placeholders (example:[{tag: "div", data: {...}}...Such as:
    [{{tag: "div", data: {attrs: {...}, slot: "you"}...}, {tag: undefined, data: undefined...}, {tag: "div", data: {attrs: {...}, slot: "my"}...}, {tag: undefined, data: undefined...}}]Copy the code
  • Declare a slots for all slots
  • If attrs contains a named slot attribute, remove the slot attribute from attrs
  • Check whether it is a named slot. If it is a named slot, store the name of the named slot in the corresponding attribute name. If it is a non-named slot, place it in the default array
  • Finally return slots (example:{ you: [VNode], my: [VNode], default: [VNode, VNode] })

Source:

    function resolveSlots(
        children, // Store a set of vNodes corresponding to slot placeholders
        context // Context, vue instance
        ) {
        var slots = {}; // Cache slots
        // Returns an empty object if there are no child nodes
        // A component can have multiple slots, so children is an array
        if(! children) {return slots
        }
        // A collection of vNodes for circular slots
        // Slots is an object. Slot is an array (stores vNodes). Slots stores slots
        for (var i = 0, l = children.length; i < l; i++) {
            var child = children[i];
            // Slot placeholder corresponds to data
            // data example: {attrs: {slot: "you"}, slot: "you"}
            var data = child.data; 
            // If the node has slot attributes, that is, named slots, remove the slot attributes in attrs
            if (data && data.attrs && data.attrs.slot) {
                delete data.attrs.slot;
            }
            If it is a named slot, store the name of the named slot in the corresponding attribute name. If it is a non-named slot, place it in the default array */
            if((child.context === context || child.fnContext === context) && data && data.slot ! =null
            ) {
                var name = data.slot; / / slot
                // If slots[name] does not exist, slots sets the slot name attribute to an empty array
                // Set slot to an empty array. Here we associate slots and slots with a reference type. When we set slot,
                // will be automatically synchronized to the corresponding slot of slots
                var slot = (slots[name] || (slots[name] = []));
                if (child.tag === 'template') {  // In this case, the tag is div
                    // Add the child of the child node to the slot slot
                    slot.push.apply(slot, child.children || []);
                } else {
                    // Add the Vnode corresponding to slot placeholders to slot slotsslot.push(child); }}else {
                // If the slot is not named, the node corresponding to the slot is inserted into the default array of slots(slots.default || (slots.default = [])).push(child); }}// Ignore slots that contain only white space
        for (var name$1 in slots) {
            // Delete an empty slot
            if (slots[name$1].every(isWhitespace)) {
                delete slots[name$1]; }}debugger
        return slots
    }
Copy the code

4-6. callHook(vm)

CallHook is a function for declaring cycles, which we’ll explain in detail in the section on declaring cycles.

4-7. initInjections(vm)

I don’t use that much, so I won’t talk about it

4-8. initProvide(vm)

I don’t use that much, so I won’t talk about it

4-9. initState(vm)

This function will be explained in [VUE source analysis [3] – VUE response]

4-10. $mount(vm)

role

The $mount method on the prototype was first cached and then redefined to restrict EL so that Vue could not be mounted on root nodes such as body and HTML.

If there is no render function, get template, which can be a #id, template string, DOM element, or if there is no template, get EL and its children as the template. CompileToFunctions parse our last generated template to generate the render function.

In fact, vue source code in two places to define the $mount method, respectively is the middle position of the file and the text at the end of the file, here first executed at the end of the position of the $mount method. Why do you do that?

The last one is for Runtime+Compiler versions. The middle mount vue.prototype.$mount method is available for Runtime Only versions.

We will parse the $mount at the end first, because our code will go there first

Source:

    vm.$mount(vm.$options.el);
    
    const mount = Vue.prototype.$mount
    
    Vue.prototype.$mount = function (
        el,  / / example: # app
        hydrating // Server-side rendering is relevant, in the browser environment we do not need to pass
        ) { 
        // Create a new dev dev
        el = el && query(el); 
        /* istanbul ignore if */
        // Warn if el is body or document
        if (el === document.body || el === document.documentElement) {
            "development"! = ='production' && warn(
                /** * Instead of mounting < HTML > or  to vue's mount, you need to mount normal elements * because mounts are overridden, if mounted on body or HTML, there are no body and HTML nodes after overridden * so we usually use div mounts. * /
                "Do not mount Vue to <html> or <body> - mount to normal elements instead."
            );
            return this
        }
        // Get parameters
        var options = this.$options;
        // resolve template/el and convert to render function
        // Parse the template /el and convert to the render function
        if(! options.render) {// Get the template string
            var template = options.template;
            if (template) { // If there is a template
                if (typeof template === 'string') {
                    // If the first string in the template is #, it is the DOM ID
                    if (template.charAt(0) = = =The '#') {
                        template = idToTemplate(template); // Get the innerHtml of the string template
                        /* istanbul ignore if */
                        if ("development"! = ='production' && !template) {
                            warn(
                                ("Template element not found or is empty: " + (options.template)),
                                this); }}}else if (template.nodeType) { // If template is the don node, get its HTML
                    template = template.innerHTML;
                } else {
                    // Is there a warning if nothing happens
                    {
                        warn('invalid template option:' + template, this);
                    }
                    return this}}else if (el) {
                // If the template does not exist and the DOM node exists, get the HTML from the DOM node to the template
                * "<div id="app"> <! --this is comment--> {{ message }} </div>" */
                template = getOuterHTML(el);

            }
            if (template) {
                /* istanbul ignore if */
                // Listen for performance monitoring
                if ("development"! = ='production' && config.performance && mark) {
                    mark('compile');
                }
                // Create a template
                var ref = compileToFunctions(
                    template, // Template string
                    {
                         //IE encodes newlines in attribute values, whereas other browsers do not
                        shouldDecodeNewlines: shouldDecodeNewlines, //flase 
                        //true Chrome encodes content in a[href]
                        shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, 
                         // Change plain text to insert delimiters. Modify the writing style of directives, such as {{MGS}} by default.
                         //delimiters: ['${', '}'] then become ${MGS}
                        delimiters: options.delimiters,
                        // When set to true, HTML comments in the template will be preserved and rendered. The default behavior is to discard them.
                        comments: options.comments 
                    },
                    this
                );
                / / ast template
                //code the argument function that the virtual DOM needs to render
                //staticRenderFns
                // This assignment can effectively prevent reference by address, resulting in data modification and other object modification problem,
                var render = ref.render;
                var staticRenderFns = ref.staticRenderFns;
                /* render is the virtual DOM that needs to be compiled */
                options.render = render;
                options.staticRenderFns = staticRenderFns;
                /* istanbul ignore if */
                if ("development"! = ='production' && config.performance && mark) {
                    mark('compile end');
                    measure(("vue " + (this._name) + " compile"), 'compile'.'compile end'); }}}// Call the $mount method on the original prototype
        // Execute mount again with $mount defined in the middle
        // After this step, the data has been rendered to the page
        return mount.call(
            this./ / the Vue instance
            el, / / true dom example: el = div # app {__vue__ : null, align: "", the title:", "lang:" ",
                / / translate: true,... }
            hydrating //undefined)};Copy the code

1 staticRenderFns

StaticRenderFns is currently an empty array, which is used to hold the render of the static content of the template, such as:

<div id="app">
    <p>This is a<span>Static content</span></p>
    <p>{{message}}</p>
</div>
Copy the code

StaticRenderFns as follows:

staticRenderFns = function () {
    with(this){return _c('p',[_v("It is"),_c('span',[_v("Static content")]])}Copy the code

5. compileToFunctions

5-1. Basic Information

Function:

Compile related functions

I’ve adjusted the order of the declarations and simplified the code to make it easier to see, so let’s look at the call structure first. The function call here looks a little convoluted, but it’s actually currified, with default parameter passing.

Source:

//
// Call compileToFunctions, passing in 3 parameters
// What does compileToFunctions do
var ref = compileToFunctions(
            template,
            {
              shouldDecodeNewlines: shouldDecodeNewlines,
              linesForHref: shouldDecodeNewlinesForHref,
              delimiters: options.delimiters, 
              comments: options.comments
            },
            this
);

//
// From the first step, compileToFunctions are functions, so compileToFunctions
Refcode1.com pileToFunctions returns a function
// ref$1 returns an object
var compileToFunctions = ref$1.compileToFunctions;
Var ref = refcode1.com pileToFunctions(template,{... },this)
    
//
The createCompiler function returns an object
// baseOptions is defined before new Vue
var ref$1 = createCompiler(baseOptions);
Var ref = createCompiler(baseOptions).compiletoFunctions (template,{... },this)

//
// The createCompilerCreator function returns an object
var createCompiler = createCompilerCreator(
     function baseCompile(template,options) {
            return {
                ast: ast, / / ast template
                render: code.render, //code the argument function that the virtual DOM needs to render
                staticRenderFns: code.staticRenderFns  / / an empty array}}); }/** * continue to convert to:  var ref = createCompilerCreator(function baseCompile(){template,options}) (baseOptions).compileToFunctions(template,{... },this) now createCompilerCreator is called. What does createCompilerCreator return

Compiled is returned by the entry parameter baseCompile,
CreateCompiler function baseCompile(template,options) {});
// returns a function that returns a wrapped object :{ast: ast, render... ,staticRenderFns:... },
The compile function also returns this object
function createCompilerCreator(
            baseCompile 
        ) {
            return function createCompiler(baseOptions) {
               function compile(template,options) {
                    var compiled = baseCompile(
                        template,
                        finalOptions
                    );
                    return compiled
                }
                return {
                    compile: compile,
                    compileToFunctions: createCompileToFunctionFn(compile)
                }
           }
}

// Compile is a function that returns an object
/** * createCompilerCreator(function baseCompile(){template,options}) (baseOptions). CompileToFunctions fetch the object returned by createCompilerCreator. The value of the createCompileToFunctionFn (compile) So, can convert again:  * var ref = createCompileToFunctionFn(function baseCompile(){template,options}) (baseOptions).compileToFunctions(template,{... },this) * yes,this is the function execution chain of our final conversion, now it is clear, CreateCompileToFunctionFn function into the compile * refs is function baseCompile () {template, the options}, The input to compileToFunctions is the argument to the above compileToFunctions (template,{... },this), which is the first input parameter */
function createCompileToFunctionFn(compile) {
        return function compileToFunctions(template, options, vm) {
            var compiled = compile(
                template,
                options
            );
            return (cache[key] = res)
        }
}
Copy the code

Now we can analyze it in detail

5-2. createCompilerCreator

Function:

Compile the template string in the Render function

Execution process:

CreateCompilerCreator: baseCompile createCompilerCreator: baseCompile createCompilerCreator: baseCompile createCompilerCreator: baseCompile createCompilerCreator BaseCompile, where baseOptions (defined before new Vue) are also passed from the previous function. Eventually all logic leads to this function, and the previous logic can be thought of as operations that provide function arguments.

  • Pass in a compiler function that returns an object
  • This object has two properties:
    • Compile function defined: the compiler that compiles template strings in the render function, responsible for compiling template strings (that is, template code for htML-like syntax) into the Render function of JavaScript syntax.
    • CreateCompileToFunctionFn: into the parameter as the compile of the definition of function, analytical template string as function

So the core of this function is actually the compile function defined in it.

Source:

    // Finally returns the compile function
        function createCompilerCreator(
            baseCompile // The basic compiler function, passed this parameter to let us in the current function
                        // Can call the incoming function and pass the parameter to it
        ) {
            return function createCompiler(baseOptions) { Figure 1 / / []
                debugger
                function compile(
                    template, 
        
{{message}}
options // example: {shouldDecodeNewlines: false... } [3]
)
{ debugger // Create a copy of the baseOptions object into the prototype proType // [图4] var finalOptions = Object.create(baseOptions); // Add basic attributes for the virtual DOM var errors = []; var tips = []; // Warning function finalOptions.warn = function (msg, tip) { (tip ? tips : errors).push(msg); }; if (options) { // Merge modules to finalOptions if (options.modules) { finalOptions.modules = (baseOptions.modules || []).concat(options.modules); } // Merge directives into finalOptions if (options.directives) { finalOptions.directives = extend(Object.create(baseOptions.directives || null), options.directives); } // Copy the other options to finalOptions for (var key in options) { if(key ! = ='modules'&& key ! = ='directives') { / / shallow copyfinalOptions[key] = options[key]; }}}/** * These two parameters will be passed as parameters to the tree: CreateCompiler = * createCompilerCreator(function baseCompile(template, options) {}) * template, options */ // var compiled = baseCompile( template, finalOptions // Add basic attributes for the virtual DOM ); { errors.push.apply(errors, detectErrors(compiled.ast)); } compiled.errors = errors; compiled.tips = tips; return compiled } /* * compile * Compile template strings in the render function. Var res = Vue.com compile('
{{MSG}}
') new Vue({data: {MSG: 'hello'}, render: res.render, staticRenderFns: res.staticRenderFns }) * * * * */
debugger return { compile: compile, compileToFunctions: createCompileToFunctionFn(compile) } } } Copy the code

Figure 1 [] baseOptions:

[figure 2] template:

[3] the options:

【 Figure 4 Compiled 】

[4] finalOptions:

5-3. baseCompile

Function:

The baseCompile function is triggered on a callback.

Source:

    // Compiler creator
        var createCompiler = createCompilerCreator(

        // Turn HTML into an AST template object, and then convert it into a function argument for virtual DOM rendering.
        // Return an object
        // {ast: ast, //ast template
        // render: code.render, //code the argument function that the virtual DOM needs to render
        //staticRenderFns: code.staticrenderfns} // Empty array

        function baseCompile(
            template, // "<div id=\"app\">\n <! --this is comment--> {{ message }}\n </div>"
            options // There is a merge of baseOptions and options.
        ) {
            
            // Returns the AST template object
            // The parse function is critical, which we will examine in the next section
            // 【图2 ast】
            var ast = parse(template.trim(), options);
            // Optimize's main function is to mark static static nodes
            // Optimize will also be resolved in a later section
            if(options.optimize ! = =false) {  
                // * Loop recursive virtual node, indicating whether or not the node is static
                //* According to node.static or node.once flag staticRoot status
                // We will parse this in the next section
                optimize(ast, options);
            }
            // Initialize the extension command, on,bind, cloak, dataGenFns to get an array,
            // There are two functions in the array genData and genData$1
            //genElement determines whether it is a component based on el, or whether it contains v-once, V-if,v-for, and template attributes.
            // Or slot slots, conversion style, CSS, etc. into the virtual DOM need to render parameter functions
            Render: ("with(this){return "+ code + "}"),staticRenderFns: state.staticrenderfns} // Empty array
            // We will parse this in the next section
            // [figure 3 code]
            var code = generate(ast, options);

            return {
                ast: ast, / / ast template
                render: code.render, //code the argument function that the virtual DOM needs to render
                staticRenderFns: code.staticRenderFns  // Static render function, array}});Copy the code

[Figure 1 Options]

【 Figure 2 AST 】

FIG. 3 Code


5-4. createCompileToFunctionFn

Function:

Perform the createCompileToFunctionFn createCompilerCreator most behind

Source:

       function createCompileToFunctionFn(compile) {
        
        var cache = Object.create(null);
        /** * returns a function whose parent function is the attribute value in createCompilerCreator: * {compile: compile, compileToFunctions: So createCompileToFunctionFn createCompileToFunctionFn (compile)} * (compile) can be seen as returns function on the current: CompileToFunctions (template, options, VM) The input to compileToFunctions is the // * */ passed when ref is defined
            
        return function compileToFunctions(
            / / is this a few into the ginseng compileToFunctions: createCompileToFunctionFn (compile)
            // Compile returns three parameters
            template,  // "
        
options, // {shouldDecodeNewlines: false,... } vm / / the Vue instance
)
{ // Shallow copy parameters options = extend({}, options); / / warning var warn$$1 = options.warn || warn; // Remove the warning in the argument delete options.warn; /* * This option is only available when compiling in a browser in the full build. * Detail: Change plain text to insert delimiters. * * example: new Vue ({delimiters: [' ${', '} ']}) / / [' ${', '} '] the String turned to "${and}" * / var key = options.delimiters ? String(options.delimiters) + template : template; if (cache[key]) { return cache[key] } BaseCompile returns the same value as the baseCompile function var compiled = compile( template, // Template string options / / parameters ); // turn code into functions var res = {}; var fnGenErrors = []; Compiled. Render creates a function to record the fnGenErrors error if it occurs // Convert the string into real JS and export it as a function // 1 createFunction // [图1 res] res.render = createFunction( compiled.render, fnGenErrors); res.staticRenderFns = compiled.staticRenderFns.map(function (code) { return createFunction(code, fnGenErrors) }); // return (cache[key] = res) } } Copy the code

5-6. createFunction

Function:

Convert the string into real JS and export it as a function

Source:

    function createFunction(
        code, / / example: "with the (this) {return _c (' div '{attrs: {" id" : "app"}}, [_v (" "+ _s (message) +" \ n ")])}"
        errors
        ) {
        debugger
        try {
            return new Function(code)
            /** * convert to function:  * (function anonymous() { with(this){return _c('div',{attrs:{"id":"app"}},[_v(" "+_s(message)+"\n ")])} }) */
        } catch (err) {
            errors.push({ err: err, code: code });
            return noop
        }
    }
Copy the code

Figure 1 Res. render, you can see res.render is already a function

[Figure 2] Cache, object with template as key

Res, render function object

At this point, the entire compilation process is parsed.

5-8. mount

When we execute at the end of mount:

return mount.call(
       this./ / the Vue instance
       el, // real dom example: el = div#app {__vue__: null, align: "", title: "", lang: "", translate: true,... }
       hydrating //undefined
)
Copy the code

It continues to mount, but the mount is defined in the middle as follows:

    Vue.prototype.$mount = function (el, hydrating) {
        // query(el) creates a new dev
        el = el && inBrowser ? query(el) : undefined;
        return mountComponent(
            this./ / the Vue instance
            el,  Align: "", title: "", lang: "", translate: true, dir: ",... }
            hydrating
        )
    };
Copy the code

mountComponent:

If the render method is not defined, the EL or template string is converted to the Render method. In Vue 2.0, all Vue components will eventually need the render method. Whether we develop the component in a single.vue file or write an EL or template attribute, this will eventually be converted to render. So this process is an “online build” process for Vue

The core of the mountComponent is to call vm._render as a virtual Node, instantiate a render Watcher, call updateComponent in its callback, and finally call vm._update to update the DOM.

    // Install components
    function mountComponent(
        vm,  / / the Vue instance
        el,  / / true dom
        hydrating // New virtual DOM vonde
    ) {
        debugger
        // Mount the real DOM to the vue instance
        vm.$el = el;
        /* Vue. Js provides 2 versions, one is Runtime + Compiler, one is Runtime only, the former contains the compiled code, can be put at Runtime, the latter does not contain the compiled code, The template needs to be pre-compiled into the render function with webpack's vue-loader. * /

        // If there is no render function in the argument, Runtime only is used
        if(! vm.$options.render) {// Instantiate the render function of the VM, the virtual DOM calls the render function of the parameter
            // Create an empty component
            vm.$options.render = createEmptyVNode;

            {
                /* istanbul ignore if */
                // If the first template in the argument is not #, the Runtime only version of the code is in this format: el: '#app',
                if ((vm.$options.template && vm.$options.template.charAt(0)! = =The '#') ||
                    vm.$options.el || el) {
                    /* You are using a Runtime only generated Vue where the template compiler is not available. Either precompile the template as a rendering function, or use a built-in compiler. * /
                    warn(
                        'You are using the runtime-only build of Vue where the template ' +
                        'compiler is not available. Either pre-compile the templates into ' +
                        'render functions, or use the compiler-included build.',
                        vm
                    );
                } else {
                    // Cannot load component: template or render functions are not defined
                    warn(
                        'Failed to mount component: template or render function not defined.', vm ); }}}// Execute the lifecycle function beforeMount
        callHook(vm, 'beforeMount');
        // Update the component
        var updateComponent;
        /* istanbul ignore if */
        // If the development environment
        /* Vue.config.performance is true to enable performance tracking for component initialization, compilation, rendering, and patching in the browser developer's performance/timeline panel */
        
        if ("development"! = ='production' && config.performance && mark) {
            updateComponent = function () {
                var name = vm._name;
                var id = vm._uid;
                var startTag = "vue-perf-start:" + id;
                var endTag = "vue-perf-end:" + id;

                mark(startTag); // Insert a name and record when the name is inserted
                var vnode = vm._render();
                mark(endTag);
                measure(("vue " + name + " render"), startTag, endTag);

                mark(startTag); // Browser performance timestamp listener
                // Update the component
                vm._update(vnode, hydrating);
                mark(endTag);
                measure(("vue " + name + " patch"), startTag, endTag);
            };
        } else {
            updateComponent = function () {
                // Update the view directly
                // [New Vue is explained in the first chapter]
                vm._update(
                    /* render is a virtual dom, The compiler functions that need to be executed are like this (function anonymous(){with(this){return _c('div',{attrs:{"id":"app"}},[_C ('input',{directives: [{name:"info",rawName:"v-info"},{name:"data",rawName:"v-data"}], attrs:{"type":"text"}}),_v(" "),_m(0)])} }) */
                    vm._render(), // Execute _render to return to the virtual Node
                    hydrating
                );
            };
        }

        // we set this to vm._watcher inside the watcher's constructor
        // since the watcher's initial patch may call $forceUpdate (e.g. inside child
        // component's mounted hook), which relies on vm._watcher being already defined
        // We set it to VM. In the observer constructor
        // Because the observer's initial patch may call $forceUpdate(e.g. Inside Child)
        // The component's mount hook), which depends on the VM. _watcher is defined
        // Create an observer
        new Watcher(
            vm,  //vm vode
            updateComponent, // Call this function back after the data binding. Update component function update view attempt
            noop, // The callback function
            null./ / parameters
            true // Whether to render the viewer
            /* isRenderWatcher */
        );
        hydrating = false;

        // manually mounted instance, call mounted on self
        // mounted is called for render-created child components in its inserted hook
        // Manually mount the instance, with the call mounted on self
        // Mount the child component created for the renderer in the inserted hook
        if (vm.$vnode == null) {
            vm._isMounted = true;
            // Execute the life cycle function Mounted
            / / render the data
            callHook(vm, 'mounted');
        }
        debugger
        return vm
    }
Copy the code

5-9

Our final ref looks like this:

Then mount the properties on ref to options:

6. optimize

What it does: Optimizes the AST after parse, marking static nodes and static root nodes. When a node staticRoots is true and is not in v-for, the subtree root of this node will be cached for the first render and retrieved directly from the cache for the next render to avoid rendering again.

The optimizer’s goal: to traverse the generated template AST tree and detect purely static subtrees, that is, DOM that never needs to be changed. Once we detect these subtrees, we can:

  1. Make them constants so we don’t need to create new nodes for them every time we re-render them;
  2. Skip them completely during the repair process. Cyclic recursive virtual node, marking whether or not the node is static. Based on the status of node.static or node.once flags staticRoot

Source:

var createCompiler = createCompilerCreator(
function baseCompile(
            template, // "<div id=\"app\">\n <! --this is comment--> {{ message }}\n </div>"
            options // There is already a merge of baseOptions and options
        ) {
            var ast = parse(template.trim(), options);
            if(options.optimize ! = =false) {  
                // * Loop recursive virtual node, indicating whether or not the node is static
                //* According to node.static or node.once flag staticRoot status
                optimize(ast, options);
}

function optimize(
    root,  // The transformed AST tree
    options / / configuration items
) {
        if(! root) {return
        }
        / / match type, tag, attrsList attrsMap, plain, the parent and children, attrs + staticKeys string
        // example: options. StaticKeys = "staticClass,staticStyle"
        isStaticKey = genStaticKeysCached(options.staticKeys || ' ');
        // Save the tag to determine whether it is an original HTML tag or an SVG tag
        isPlatformReservedTag = options.isReservedTag || no;
        
        // Step 1: mark all non-static nodes.
        markStatic$1(root);
        
        // Step 2: mark all static root nodes
        markStaticRoots(root, false);
}

Copy the code

6-1 markStatic$

Function:

The first step is to mark all non-static nodes.

Iterate the recursive child node and ifConditions virtual node respectively to mark whether the child node is static or not. If the child node is non-static, then the parent node is also non-static.

Source:

    function markStatic$1(node) {
        debugger
        // Check whether this node can be static.
        node.static = isStatic(node);   
        if (node.type === 1) {
            // Do not set component slot content to static. This avoids:
            // 1. Components cannot change slot nodes
            // 2. Static slot content cannot be hot loaded
            if (
                !isPlatformReservedTag(node.tag) &&   // Determine whether it is an ORIGINAL HTML tag (div, etc.) or an SVG tagnode.tag ! = ='slot' &&  // The current label is not equal to slot
                node.attrsMap['inline-template'] = =null  // Also not inline-template inline template
            ) {
                return
            }
            // Recursively loop the child node, if the child is not static, then neither is the parent node
            for (var i = 0, l = node.children.length; i < l; i++) {
                var child = node.children[i];
                markStatic$1(child);
                if(! child.static) { node.static =false; }}If conditions = [{exp: "(_f(\"recordType\")(texts)))==='checkbox'", block: {...}} {exp: "(_f (\" recordType \ ") (texts)) = = = 'radio' ", block: {...}} {exp: undefined, block: {...}}] determines if the virtual DOM of the if array is static, if not, then the parent node is not */
            if (node.ifConditions) { 
                for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1{+ +)var block = node.ifConditions[i$1].block;  / / virtual dom
                    markStatic$1(block);
                    if(! block.static) { node.static =false;
                    }
                }
            }
        }
    }
    

The static AST virtual DOM type must be different from 2 and 3, and pre must be true
function isStatic(node) {
        debugger
        if (node.type === 2) { // If node is an expression, it is marked as a non-static node
            return false
        }
        if (node.type === 3) { // text a text node or an empty comment node
            return true
        }
        // If it is neither an expression nor a text node, it is a tag with child nodes, based on some attributes of the tag
        // Check whether a node is static or not.
        return!!!!! (// Other nodes such as the 'div' tag
            node.pre ||   // Check whether the tag has a V-pre instruction, if so true
            (
                !node.hasBindings && // There is no dynamic tag element! node.if && ! node.for &&// 没有 v-if 或者 v-for 或者 v-else! isBuiltInTag(node.tag) &&/ / no slot, component
                isPlatformReservedTag(node.tag) && // Is reserved tag, HTML original tag or SVG tag
                Returns false if the parent tag of the current AST virtual DOM is not template or true if it contains v-for! isDirectChildOfTemplateFor(node) &&/ / the node must each key type, tag, attrsList, attrsMap, plain, such as the parent of a string
                Object.keys(node).every(isStaticKey) 
            )
        )
}
Copy the code

6-2 markStaticRoots

Function:

The second step, marking the static root, is similar in logic to the first step

Source:

function markStaticRoots(node, IsInFor) {debugger if (the node type = = = 1) {if (node. The static | | / / static node node. Once / / v - once only rendering node at a time.) { node.staticInFor = isInFor; } if (node.static && // node.children. Length && // if there are children! (Node.children. Length === 1 && // If there is only one child Node.children [0].type === 3 // text node)) {node.staticroot = true; Return} else {node.staticroot = false; } if (node.children) { for (var i = 0, l = node.children.length; i < l; i++) { markStaticRoots( node.children[i], isInFor || !! node.for ); } } if (node.ifConditions) { for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) { markStaticRoots( node.ifConditions[i$1].block, isInFor ); }}}}Copy the code

Finally, let’s look at the virtual DOM after the tag is completed:

7. generate

7-1. Basic information

Function:

Generate render expression

After optimizing the AST previously, you need to turn the entire AST into an executable block of code, the Render function. The template compiler uses generate to generate code for the AST.

Source:

var createCompiler = createCompilerCreator(
function baseCompile(
            template, // "<div id=\"app\">\n <! --this is comment--> {{ message }}\n </div>"
            options // There is already a merge of baseOptions and options
        ) {
            var ast = parse(template.trim(), options);
            if(options.optimize ! = =false) {  
                // loop recursive virtual node, indicating whether it is a static node
                // Specify the staticRoot status according to node.static or node.once
                optimize(ast, options);
            }
            var code = generate(ast, options);
            return {
                ast: ast,
                render: code.render, //code the argument function that the virtual DOM needs to render
                staticRenderFns: code.staticRenderFns  / / an empty array}}Copy the code

The code returned is in the following format, which is divided into render and staticRenderFns

{ 
    render: "With (this) {return _c (' div '{attrs: {\" id \ ": \" app \}}, [_c (' div', [_v (\ \ "" I'm a static node)]), _v (\" \ "), (showDom)? _m (0) : _e ()])}".staticRenderFns: ["With (this){return _c('div',[_v(\" I only render once \")])}"]}Copy the code

generate

    function generate(ast, options) {
        
        // Generate state
        // * Extension instruction, on,bind, cloak, method
        // * dataGenFns gets an array containing genData and genData$1
        var state = new CodegenState(options);
        /** * Render parameter function * code, for example: /** * render parameter function * code, for example: "_c (' div ', {attrs: {" id" : "app"}}, [_c (' div ', [_v (" I am a static node ")]), _v (" "), (showDom)? _m (0) : _e ()]) "* /
        debugger        
        var code = ast ? genElement(ast, state) : '_c("div")';
        debugger
        return {
            //with binds js to this abbreviation
            render: ("with(this){return " + code + "}"),
            staticRenderFns: state.staticRenderFns / / an empty array}}Copy the code

Let’s look at the generated state. DataGenFns is an array, and there are two functions in the array genData and genData$1

7-2. CodegenState

Function:

The extension,on,bind, cloak, dataGenFns, retrieves an array containing two functions genData and genData$1

Source:

   var CodegenState = function CodegenState(
        options // Basic configuration
        ) {
        this.options = options;
        this.warn = options.warn || baseWarn; // Warning log output function
        // Get an array of objects with keys transformCode []
        this.transforms = pluckModuleFunction(options.modules, 'transformCode');
        ƒ genData(el), ƒ genData$1(el)
        this.dataGenFns = pluckModuleFunction(options.modules, 'genData');

        // Extend instructions, on,bind, cloak, methods
        this.directives = extend(
            extend(
                {},
                baseDirectives
            ),
            options.directives
        );
        // Save the tag to determine whether it is an original HTML tag or an SVG tag
        var isReservedTag = options.isReservedTag || no; 
        // If it is not a native tag, it may be a component
        this.maybeComponent = function (el) {
            return! isReservedTag(el.tag);// Native tags
        };
        this.onceId = 0;
        // Static render method
        this.staticRenderFns = [];
    };
Copy the code

7-3. genElement

Function:

GenElement’s logic starts with code generation for the attributes of the current element, such as V-if, V-for, and so on. This is followed by block generation of the body part of the element

Source:

    function genElement(
        el, // Ast object or virtual DOM
        state // Some methods to render the virtual DOM
    ) {
        debugger
        if(el.staticRoot && ! el.staticProcessed) {// Export the child node as an argument to the virtual DOM rendering function. Static rendering
            return genStatic(el, state)
        } else if(el.once && ! el.onceProcessed) {https://cn.vuejs.org/v2/api/#v-once / / reference document
            // v-once
            // No expression is required
            // Detail: render elements and components only once. In subsequent re-rendering, the element/component and all its child nodes are treated as static and skipped. This can be used to optimize update performance
            / / <! -- Single element -->
            // <span v-once>This will never change: {{msg}}</span>
            return genOnce(el, state);
        } else if(el.for && ! el.forProcessed) {// v-for
            // Check whether the tag contains the V-for attribute. Parse the parameters in the V-for directive and return the parameter js rendering function required by the virtual DOM
            return genFor(el, state)
        } else if(el.if && ! el.ifProcessed) {// Check whether the tag has an if attribute
            // v-if
            // If the tag contains an if attribute parses the parameters in the if directive and returns the parameters required by the virtual DOM
            return genIf(el, state)
        } else if (el.tag === 'template' && !el.slotTarget) {
            // The tag is the template template
            // Get the virtual DOM child node
            return genChildren(el, state) || 'void 0'
        } else if (el.tag === 'slot') {
            // If the label is a slot
            return genSlot(el, state)
        } else {
            // component or element
            // Component or element
            var code;
            if (el.component) { // If it is a component

                // Create a function to render the parameters of the virtual DOM
                code = genComponent(
                    el.component,
                    el,
                    state
                );
            } else {

                var data = el.plain ?  // This flag is true if there are no attributes in the tag
                    undefined :
                    genData$2(el, state);

                var children = el.inlineTemplate ? // Is the template tag inline
                    null :
                    genChildren(el, state, true);
                code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : ' ') + (children ? ("," + children) : ' ') + ")";
            }

            // module transforms
            for (var i = 0; i < state.transforms.length; i++) {
                code = state.transforms[i](el, code);
            }
            // Returns the js rendering function required by the virtual DOM
            return code
        }
    }
Copy the code

7-4. genStatic

Function:

Static marking

Source:

    function genStatic(el, state) {
        debugger
        // The tag is already statically processed
        el.staticProcessed = true;
        // Add a rendering function
        // Because el. StaticProcessed is set to true, a recursive call to genElement will enter
        // Determine the else if inside
        state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}"));

        // Returns the parameter format required for virtual DOM rendering
        return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : ' ') + ")")}Copy the code

7-5. genOnce

Function:

Source:

    function genOnce(el, state) {
        // The flag has been processed
        el.onceProcessed = true;
        //
        if(el.if && ! el.ifProcessed) {// Check whether the tag contains an if attribute
            return genIf(el, state)
        } else if (el.staticInFor) {
            var key = ' ';
            
            var parent = el.parent;
            while (parent) {
                if (parent.for) {
                    key = parent.key;
                    break
                }
                parent = parent.parent;
            }
            if(! key) {"development"! = ='production' && state.warn(
                    "v-once can only be used inside v-for that is keyed. "
                );
                //genElement determines whether it is a component according to the EL, or whether it contains V-once, V-if, V-for, whether it has the template attribute, or slot slot, converting style, CSS, etc
                return genElement(el, state)
            }
            //genElement determines whether it is a component according to the EL, or whether it contains V-once, V-if, V-for, whether it has the template attribute, or slot slot, converting style, CSS, etc
            return ("_o(" + (genElement(el, state)) + "," + (state.onceId++) + "," + key + ")")}else {
            // Export the child node as an argument to the virtual DOM rendering function
            return genStatic(el, state)
        }
    }
Copy the code

If, el.if is defined in function processIf

    // Get v-if attribute, add V-if, V-eles, V-else -if attribute to el virtual DOM
    function processIf(el) {
        var exp = getAndRemoveAttr(el, 'v-if'); // Get the V-if attribute
        if (exp) {
        // 
      
I only render once
el.if = exp; addIfCondition(el, { // Add a tag to the if directive exp: exp, block: el }); } else { if (getAndRemoveAttr(el, 'v-else') != null) { el.else = true; } var elseif = getAndRemoveAttr(el, 'v-else-if'); if(elseif) { el.elseif = elseif; }}}Copy the code

7-6. genIf

Function:

Parsing the argument in the if directive, if present, returns a ternary expression, here is the source of the V-if

Source:

function genIf(el, state, altGen, altEmpty) {
        el.ifProcessed = true; // Avoid recursion is already handled
        // el.ifconditions.slice () if condition parameter
        // Parses the arguments in the if directive and returns the arguments required by the virtual DOM
        return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}

function genIfConditions(
        conditions, //[{exp: view if attribute,block: el virtual component currently rendered}] example: [{exp: "showDom", block: {...}}]
        state,  // CodegenState {dataGenFns: (2) [ƒ, ƒ] }
        altGen,  // undefined does not see where the entry parameter
        altEmpty // undefined does not see where the entry parameter
    ) {
        debugger
        if(! conditions.length) {// An empty virtual DOM parameter is returned if conditions does not exist
            return altEmpty || '_e()'
        }
        var condition = conditions.shift();  // take the first term
        /** * Determines whether the if argument exists * if it does, the condition. Block data is then recursed to true and will not be processed again ** */
        if (condition.exp) {  
            /** * genTernaryExp(condition. Block) example: "_m(0)" "_e()" * final expression :(showDom)? _m(0):_e() */
            return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
        } else {
            return ("" + (genTernaryExp(condition.block))) // No expression directly generates elements like v-else
        }
        
    // If you use v-once to generate the image of (a)? _m(0): code like _m(1)
    function genTernaryExp(el) {
            // The data will now be true and will not come in again
            return altGen ?
                altGen(el, state)  //altGen a custom function
                : el.once ?     // Static label flag does not exist
                    genOnce(el, state)  // Export the virtual DOM parameter of a static label
                    : genElement(el, state) // Recursive EL data will now be true and will not come in again}}Copy the code