vue data initialization -- initState

Data initialization

vue instance will run a series of initialization operations when it is established. Among these initialization operations, initState is the most associated with data binding.

First, let's look at his code:

function initState(vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if(opts.props) {
        initProps(vm, opts.props); //Initialize props
    }
    if(opts.methods) {
        initMethods(vm, opts.methods); //Initialization methods
    }
    if(opts.data) {
        initData(vm); //Initialize data
    } else {
        observe(vm._data = {}, true /* asRootData */ );
    }
    if(opts.computed) {
        initComputed(vm, opts.computed); //Initialize computed
    }
    if(opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch); //Initialize watch
    }
}

In the initialization of so much data, props, methods and data are relatively simple (so I won't introduce them in detail) ☺), While computed and watch are relatively difficult and complex in logic, so I will mainly talk about computed and watch (the following code is simplified).

initState mainly initializes props, methods, data, computed and watch data in vue instances.

When initializing props (initProps), it will traverse each attribute in props, and then carry out type verification, data monitoring, etc. (provide a hook function that throws a warning when assigning a value to the props attribute).

When initializing methods (initMethods), it mainly monitors whether the method name in methods is legal.

When initializing data (initData), the observe function will be run to deeply traverse each attribute in the data to hijack the data.

When initializing computed (initComputed), it will monitor whether the data already exists on data or props. If so, it will throw a warning. Otherwise, it will call the defineComputed function to listen to the data and bind getters and setters for the properties in the component. If the value of the attribute in computed is a function, it defaults to the getter function of the attribute. In addition, the value of an attribute can also be an object. It has only three valid fields set, get and cache, which respectively represent the setter, getter and whether caching is enabled of the attribute. Get is required, and cache is true by default.

function initComputed(vm, computed) {
    var watchers = vm._computedWatchers = Object.create(null);

    for(var key in computed) {
        var userDef = computed[key];
        var getter = typeof userDef === 'function' ? userDef : userDef.get;

        //Create a calculated property watcher
        watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions
        );

        if(!(key in vm)) {
            //If the defined calculation attribute is not on the component instance, data hijacking is performed on the attribute
            //defineComputed is very important. Let's talk about it later
            defineComputed(vm, key, userDef);
        } else {
            //If the defined calculation attribute is in data and props, a warning is thrown
        }
    }
}

When initializing the watch (initWatch), vm.com will be called$ The watch function binds a setter callback for the attribute in the watch (if there is no such attribute in the component, it cannot be listened to successfully, and the attribute must exist in props, data or computed). If the value of the property in watch is a function, it defaults to the setter callback function of the property. If the value of the property is an array, it will traverse the contents of the array and bind callbacks to the property respectively. In addition, the value of the property can also be an object. At this time, the handler field in the object represents the setter callback function, immediate represents whether to execute the handler method inside immediately, and deep represents whether to listen deeply.

vm.$ The watch function uses watcher directly to build the observer object. The value of the attribute in the watch is used as the watcher CB exists, when the observer update s, in the watcher Execute in the run function. To understand this process, you can see the introduction of Watcher in my previous vue responsive system - observe, watch and dep.

function initWatch(vm, watch) {
    //Traverse the watch and create a listener for each attribute
    for(var key in watch) {
        var handler = watch[key];
        //If the property value is an array, traverse the array and create multiple listeners for the property
        //The createWatcher function encapsulates VM$ Watch, will be in VM$ Create listener in watch
        if(Array.isArray(handler)) {
            for(var i = 0; i < handler.length; i++) {
                createWatcher(vm, key, handler[i]);
            }
        } else {
            //Create listeners for properties
            createWatcher(vm, key, handler);
        }
    }
}

function createWatcher(vm, expOrFn, handler, options) {
    //If the property value is an object, take the handler property of the object as the callback
    if(isPlainObject(handler)) {
        options = handler;
        handler = handler.handler;
    }
    //If the property value is a string, look for it from the component instance
    if(typeof handler === 'string') {
        handler = vm[handler];
    }
    //Create listeners for properties
    return vm.$watch(expOrFn, handler, options)
} 

Guangzhou brand design companyhttps://www.houdianzi.com PPT template downloadhttps://redbox.wode007.com

computed

Computed is essentially an observer of lazy evaluation, with caching. Only when the dependency changes and the computed attribute is accessed for the first time, will the new value be calculated

The following will explain around this sentence.

As mentioned in the above code, when the data in the calculation attribute exists in data and props, it will be warned, that is, this method is wrong. Therefore, generally, we will declare data directly in the calculation attribute. In the same code fragment, if the defined calculation attribute is not on the component instance, the defineComputed function will be run to hijack the data. Let's take a look at what is done in the defineComputed function.

function defineComputed(target, key, userDef) {
    //Is it server-side rendering
    var shouldCache = !isServerRendering();
    //If we write the value of the calculated property as a function, the function defaults to get of the calculated property
    if(typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache ?
            //If it is not a server-side rendering, the cache is used by default, and get is set to the cache function created by createComputedGetter
            createComputedGetter(key) :
            //Otherwise, do not use the cache, and directly set get to userDef, a function defined by us
            userDef;
        //set to null function
        sharedPropertyDefinition.set = noop;
    } else {
        //If we write the value of the calculated attribute as an object, the object may contain three fields: set, get and cache
        sharedPropertyDefinition.get = userDef.get ?
            shouldCache && userDef.cache !== false ?
            //If we pass in the get field, and it is not rendered by the server, and the cache is not false, set get to the cache function created by createComputedGetter
            createComputedGetter(key) : 
            //If we pass in the get field, but the server rendering or cache is set to false, set get to userDef, which is the function we define
            userDef.get :
            //If no get field is passed in, set get to an empty function
            noop;
        //Set to our incoming set field or empty function
        sharedPropertyDefinition.set = userDef.set ?
            userDef.set :
            noop;
    }
    //Although both get and set can be set as null functions here
    //However, in the project, if get is empty, an error will be reported for the data value, and if set is empty, an error will be reported for the data value assignment
    //The main function of computed is to calculate the value, so the get field is required
    
    //Data hijacking
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

In the previous "vue responsive system - observe, watcher and dep", I mentioned in my introduction to Watcher that when calculating the instantiation of attribute watcher, options will be used Lazy is set to true, which is the key to the lazy evaluation of computing attributes and cacheability. Of course, the premise is that the cache is not false.

If the cache is not false, the createComputedGetter function will be called to create the getter function computedGetter to calculate the attribute,

Let's start with a piece of code

function createComputedGetter(key) {
    return function computedGetter() {
        var watcher = this._computedWatchers && this._computedWatchers[key];
        if(watcher) {
            if(watcher.dirty) {
                //watcher. Update the value of the watcher in evaluate and put the watcher Dirty is set to false
                //In this way, the Watcher will not be updated until the next dependency update Dirty is set to true, and then the function will be run again when the value is taken
                watcher.evaluate();
            }
            //Dependency tracking
            if(Dep.target) {
                watcher.depend();
            }
            //Returns the value of the watcher
            return watcher.value
        }
    }
}

//For the calculated attribute, when taking the calculated attribute, it is found that the dirty of the watcher of the calculated attribute is true
//It means that the data is not up-to-date and needs to be recalculated. Here is the value of the recalculated attribute.
Watcher.prototype.evaluate = function evaluate() {
    this.value = this.get();
    this.dirty = false;
};

//When a dependency changes, notify it to update
Watcher.prototype.update = function update() {
    //There are three kinds of watchers. Only the lazy of the calculated attribute watcher is set to true, which means that lazy evaluation is enabled
    if(this.lazy) {
        this.dirty = true;
    } else if(this.sync) {
        //For the direct run marked as synchronous calculation, the three types are not available, so the following queueWatcher will be followed
        this.run();
    } else {
        //Push the watcher into the observer queue and call it when the next tick.
        //That is, data changes are not updated immediately, but asynchronously in batches
        queueWatcher(this);
    }
};

When options After lazy is set to true (only the options.lazy of the attribute watcher is set to true), each dependency update will not actively trigger the run function, but set the watcher Dirty is set to true. In this way, when calculating the value of the attribute, the computedGetter function will be run. The computedGetter function has a function about the watcher Dirty's judgment, when the watcher When dirty is true, Watcher will run Evaluate updates the value and puts the watcher Dirty is set to false, which completes the lazy evaluation process. As long as the dependency is not updated later, update will not be run and the Watcher will not be updated If dirty is true, the Watcher will not run when the value is retrieved again Evaluate updates the value to achieve the effect of caching.

To sum up, we know that when the cache is not false, the calculated attributes are evaluated lazily and cached, while the cache is true by default, and we mostly use this default value. Therefore, we say that computed is essentially an observer of lazy evaluation and caching. Only when the dependency changes and the computed attribute is accessed for the first time, can a new value be calculated.

Tags: Framework

Posted by rajsekar2u on Mon, 02 May 2022 09:30:01 +0300