Vue source code analysis [3] – VUE response type

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; }},watch: {// It was not written at the beginning, but added in the chapter of Watch
                money: {handler(newVal, oldVal){
                        this.msgText = newVal+this.msgText
                    },
                    deep:true.immediate:true}},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.


Concept 2.

2-1. What is the Vue response

When the data changes, the page is re-rendered, which is called Vue responsiveness

2-2. What do we need to do to complete this process

  • Detect changes in data
  • Collect what data the view depends on
  • When data changes, automatically “notify” the part of the view that needs to be updated and update it

They correspond to professional idioms:

  • Data hijacking/data proxy
  • Depend on the collection
  • Publish and subscribe model

3-3. Translation translation

Ma Zi Chang: The data has changed, it will be re-rendered, you translate for the translator

We need to know which data has changed (data hijacking), we need to know which data has changed, we need to know which data is being used by the view (dependent on collection), and finally, we need to tell the page that the data you depend on has changed, so update it now (publish subscribe mode).

4. 4

To make it easier for you to read, introduce some basic concepts.

  • Watcher: An observer
    • We can think of Watcher as an intermediary, notifying it when data changes, and then notifying it elsewhere.
  • Dep: Subscriber
    • Collecting dependencies requires finding a place to store dependencies for dependencies, so we created a Dep that collects dependencies, removes dependencies, sends messages to dependencies, and so on. Dep is used for dependency collection and dispatch update operations that decouple attributes, “to be more specific” : its main purpose is to store Watcher observer objects.

3. Overall process

Vue instantiates an object as follows:

  1. After a new instance is created, Vue calls compile to convert el to VNode.
  2. Call initState to create hooks for props, data, and observers for its object members (adding getters and setters).
  3. Mount, set up a Watcher directly corresponding to Render at mount time, and compile the template to generate render function, execute vm._update to update the DOM.
  4. Whenever data changes, the corresponding Watcher is notified to perform a callback to update the view.
    • When a value is assigned to an attribute of the object, the set method fires.
    • The set call triggers notify() of the Dep to notify the corresponding Watcher of the change.
    • Watcher calls the update method.

In the process:

  • An Observer is used to add Dep dependencies to data.
  • Dep is data. Each object, including its children, has one of these objects. When the bound data changes, Watcher is notified through dep.notify().
  • Compile is an HTML instruction parser that scans and parses the instructions of each element node, replaces the data according to the instruction template, and binds the corresponding update function.
  • Watcher is a bridge between the Observer and Compile. When Compile parses the directive, it creates a Watcher and binds the update method to the Dep object.
  data:{
      Dep:{
          watch1,
          watch2,
      }
  }
Copy the code

3. initState

3-1. Basic information

The state new Vue is initialized, and function initMixin is executed to initialize each state.

Function:

  • Initialization state: Initializes them separatelyProps, Methods, Data, computed, and WatchIt is also a data responsive entry point
  • priority:Props, Methods, data, computedAttributes in objects must be unique, and their priorities are the same as those in the list order. Keys in computed cannot be the same as keys in props and data, and methods have no impact

Source:

    function initState(vm) {
        vm._watchers = []; // Initialize the observer queue
        var opts = vm.$options; // Initialize parameters
        // Check if there is an props attribute, and if there is an observer, add the props attribute
        $options = mergeOptions() vm.$options = mergeOptions()
        // It is mounted here
        if (opts.props) {
            // Initialize the props to check whether the props data format is canonical. If so, add it to the observer queue
            initProps(vm, opts.props);
        }
        if (opts.methods) {
            Methods bubble events into the outermost layer of vm[key] virtual DOM
            initMethods(vm, opts.methods);
        }
        if (opts.data) {
            // Initialize data to get options.data and add them to listener
            initData(vm);
        } else {
            // If value has an __ob__ instantiation dep object, add an __ob__ attribute to value.
            // Adding vm._data to the Observer returns the object instantiated by the new Observer
            observe(vm._data = {}, true /* asRootData */);

        }
        if (opts.computed) { // Calculate attributes
            // Initialize the calculated property and determine if the property's key is in data, add the calculated property's key to the listener
            initComputed(vm, opts.computed);
        }
        The watch / / options
        if(opts.watch && opts.watch ! == nativeWatch) {// Initialize WatchinitWatch(vm, opts.watch); }}Copy the code

3-2. initProps

Function:

Initialize data Gets data for options.props, sets a response for each property of the props object, and proxies it to the VM instance

Source:

if (opts.props) {
      // Initialize the props to check whether the props data format is canonical. If so, add it to the observer queue
      initProps(vm, opts.props);
}

// Initialize the props to check whether the props data format is canonical. If so, add it to the observer queue
function initProps(
        vm, 
        propsOptions // Props example: {msginfo: {type: null}, totalNum: {type: null}}
                    // This format is processed by the normalizeProps function
        ) {
        debugger
        {msginfo: "totalnum: 1200 ",totalnum: 1200}
        var propsData = vm.$options.propsData || {}; 
        var props = vm._props = {}; / / mount _props
        var keys = vm.$options._propKeys = []; / / mount _propKeys
        varisRoot = ! vm.$parent;// If the $parent attribute exists, this node is not the root node
        // Set not to listen to observers
        if(! isRoot) { toggleObserving(false);  // shouldObserve = false;
        }
        // Pass the props key through the function
        var loop = function (key) {
            debugger. };// Loop to check whether props are qualified data and add observers
        for (var key in propsOptions) loop(key);
        toggleObserving(true);
}
Copy the code

Loop:

Obtain the default value of props[key], and verify prop

var loop = function (key) {
            debugger
            keys.push(key);
            // Get the default value of props[key]
            var value = validateProp(
                key, // Key of the props object. Example: "totalnum"
                propsOptions, // Example: {msginfo: {type: null}, totalNum: {type: null}}
                propsData, {msginfo: "totalnum: 1200 ",totalnum: 1200}
                vm
            );
            /* Istanbul ignore else */
            {
                // The hump aBc becomes a-bc
                // Match uppercase letters and replace both sides with - before converting to lowercase
                var hyphenatedKey = hyphenate(key);
                // Check whether the attribute is reserved.
                //var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');
                if (isReservedAttribute(hyphenatedKey) ||
                    config.isReservedAttr(hyphenatedKey)) {
                    // Output a warning
                    warn(
                        ("\" " + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
                        vm
                    );
                }

                Subscribers are notified of new value changes by defineProperty's set method
                defineReactive(props, key, value, function () {
                    if(vm.$parent && ! isUpdatingChildComponent) { warn("Avoid mutating a prop directly since the value will be " +
                            "overwritten whenever the parent component re-renders. " +
                            "Instead, use a data or computed property based on the prop's " +
                            "value. Prop being mutated: \"" + key + "\" ", vm ); }}); }// static props are already proxied on the component's prototype
            // during Vue.extend(). We only need to proxy props defined at
            // instantiation here.
            if(! (keyin vm)) { // If the VM doesn't have props, add it to the VM so that this.[propsKey] gets the value
                proxy(vm, "_props", key); }};Copy the code

3-2-1. validateProp

Execution process:

  • Check whether prop type is a Boolean value
  • Define a value as the value of the key in the props object, perform a series of operations on it, and finally return it
  • If type is a Boolean
    • Set value to false if key is not propsData’s own property and default is not defined
    • Set value to true if value is null, or value is the same as key, and type is not a string, or if the Boolean index is less than a string, it matches a Boolean type first
  • If value is not defined, try to get the default value and add observations to value
  • Check whether prop is qualified
  • Returns the final value

Source:

    function validateProp(
        key, // Key of the props object. Example: "totalnum"
        propOptions, // Example: {msginfo: {type: null}, totalNum: {type: null}}
        propsData, {msginfo: "totalnum: 1200 ",totalnum: 1200}
        vm
    ) {  / / vm this attribute

        var prop = propOptions[key]; // Get props attributes of a component definition. Example: {type: null}
        varabsent = ! hasOwn(propsData, key);// Whether key is not its own attribute
        var value = propsData[key]; // Get a value example: "Excellent Negu:"
        / * * * prop. Type: * * getTypeIndex = null * * getTypeIndex = null * * getTypeIndex = null * * getTypeIndex = null * * getTypeIndex = null * * getTypeIndex * Check whether the type in props meets the desired type, if so, return 0 or subscript, otherwise -1 */
        var booleanIndex = getTypeIndex(Boolean, prop.type); 
        if (booleanIndex > -1) { // If the value is Boolean
            // Set value to false if key is not propsData instantiated, or if default is not defined
            if(absent && ! hasOwn(prop,'default')) { 
                value = false;
            } else if (
                value === ' '  // If value is null
                || value === hyphenate(key) // Or key out - when the form is the same as value
            ) { 
                // Check whether prop.type is a string
                var stringIndex = getTypeIndex(String, prop.type);
                // If it is not a string,
                // If the Boolean index is less than the string, the Boolean type is matched first.
                // Force the value to true
                if (
                    stringIndex < 0 ||
                    booleanIndex < stringIndex) { 
                    value = true; }}}debugger
        // If value is not defined
        if (value === undefined) {  
             // Try to get the default value
            value = getPropDefaultValue(vm, prop, key);
            var prevShouldObserve = shouldObserve;
            toggleObserving(true);
            observe(value);
            toggleObserving(prevShouldObserve);
        }
        {
            // Check whether prop is qualified
            assertProp(
                prop, // The type value of the attribute
                key, // Key in the props property
                value, // View property value
                vm, // VueComponent component constructor
                absent //false
            );
        }
        return value
    }
Copy the code
3-2-1-1. getPropDefaultValue

Function:

Return the default value of the prop property.

Execution process:

  • If there is no default on prop, return undefined
  • If prop’s default is a function and type is not a function declaration, defalut is returned, otherwise defalut is returned

Associated documents:

If (props) [] or {} is the default value, an error will be reported. If (props) [] or {} is the default value, an error will be reported.

SelectLogisticsNos: {type: Array, default: false // Props: {visible: {type: Boolean, default: false}, selectLogisticsNos: {type: Array, default: () = > [] / / / / Object/Array must use the plant function default: [] / / complains}}Copy the code

So why do it? Because when our objects operate, they need to point their this to the VM instance.

Source:

    function getPropDefaultValue(vm, prop, key) {
        // Check whether default in prop is instantiated by prop
        if(! hasOwn(prop,'default')) {
            return undefined
        }
        var def = prop.default;
        /** * If the default value of the property key is invalid, the factory function must be used for properties of type Object/Array. Example: {visible: {type: Boolean, default: SelectLogisticsNos: {type: Array, default: () => [] //Object/Array must use factory functions},} */
        if ("development"! = ='production' && isObject(def)) {
            warn(
                'Invalid default value for prop "' + key + '" : +
                'Props with type Object/Array must use a factory function ' +
                'to return the default value.',
                vm
            );
        }
        // The original PROP value was also not defined in the previous render,
        // Return the previous default value to avoid unnecessary monitoring triggers
        if (vm && vm.$options.propsData &&
            vm.$options.propsData[key] === undefined&& vm._props[key] ! = =undefined
        ) {
            return vm._props[key]
        }
        // prop default is a function, and type is not a function, then defalut is executed, otherwise defalut is returned
        return typeof def === 'function'&& getType(prop.type) ! = ='Function'
            ? def.call(vm)
            : def
    }
Copy the code

3-3. initMethods

Function:

Apply a series of constraints to functions, fix methods objects, and mount individual functions directly to vUE

Execution process:

  • Iterate over the methods object
  • Constraints on functions
    • Raises a warning if the event corresponding to the key of the function object does not exist
    • If a key is defined in a property, the same key cannot be defined in methods
    • Check if a string is specified$Or a letter beginning with _. Events cannot begin with$or_Initial letter
  • Mount the event directly to the VUE, giving an empty function if the function is empty, and returning a function function if it exists

Associated documents:

When we use events in vue, we use this.fn() directly, i.e. Vue.fn(). So why does it work that way?

It is because the initMethods last step mounts each function directly to the VM.

Source:

if (opts.methods) {
     Methods bubble events into the outermost layer of vm[key] virtual DOM
     initMethods(vm, opts.methods);
}

function initMethods(
        vm, 
        methods ƒ getMoreMoney()}
        ) {
        debugger 
        var props = vm.$options.props;
        // Loop methods event objects
        for (var key in methods) {
            {
                // If the event corresponding to the key does not exist, a warning is issued
                if (methods[key] == null) {
                    warn(
                        "Method \"" + key + "\" has an undefined value in the component definition. " +
                        "Did you reference the function correctly?",
                        vm
                    );
                }
                // If a key is defined in an attribute, the same key cannot be defined in methods
                if (props && hasOwn(props, key)) {
                    warn(
                        ("Method \"" + key + "\" has already been defined as a prop."),
                        vm
                    );
                }
                // isReserved Checks whether a string starts with a letter $or _
                // Events cannot start with a letter $or _, because methods starting with $and _ are generally built-in methods that repeat
                if ((key in vm) && isReserved(key)) { 
                    warn(
                        "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
                        "Avoid defining component methods that start with _ or $."); }}// Put the event in the outermost object, give an empty function if the function is empty, and return the function if it has a function
            vm[key] = methods[key] == null? noop : bind(methods[key], vm); }}Copy the code

3-4. initData

InitState (VM) : Vue

$attrs: (...) $listeners: [] $createElement: ƒ (a, B, c, D) $options: {components: {... }, the methods: {... }, el: "# app... $parent: undefined $refs: {} $root: Vue {_uid: 0, _isVue: true, $options: {... }, _renderProxy: Proxy, _self: Vue,... $scopedSlots: {} $slots: {} $vNode: undefined _c: ƒ (a, b, c, d) arguments: (...) caller: (...) Prototype: {constructor: ƒ} __proto__: ƒ () [[FunctionLocation]]: oldvue.js :6695 [[FunctionLocation]]: Scopes: Scopes[3] _directInactive: false _events: {} _hasHookEvent: false _inactive: null _isBeingDestroyed: False _isVue: false _isVue: true _isVue: false _isVue: true _renderProxy: Proxy {_uid: 0, _isVue: true, $options: {... }, _renderProxy: Proxy, _self: Vue,... } _self: Vue {_uid: 0, _isVue: true, $options: {... }, _renderProxy: Proxy, _self: Vue,... } _staticTrees: null _uid: 0 _vnode: null _watcher: null $data: (...) $isServer: (...) $props: (...) $ssrContext: (...) ƒ reactiveGetter() set $notices: ƒ reactiveSetter (newVal) __proto__ : ObjectCopy the code

As you can see, the VM is already mounted with the common properties and functions.

Function:

Initialize data to obtain options.data, evaluate data, methods, and props, delegate each value of data to vm, and add them to listener.

Execution process:

  • Reset data, if data is a function, take the return of data, not its own value, and mount the _data attribute (object) of data to Vue.
  • After the reset, data is not an object, reset data to {} with a warning
  • Traverse the key in the data object, warning if the key is the same as the property name in methods or props
  • If the key does not start with $or _, proxy properties on the data object to the VM instance
  • Set up a responsivity for data on a data object

Associated documents:

When we use the data attribute in vue, we directly use this. XXX, i.e. Vue.xxx. So why does it work that way?

Proxy (VM, “_data”, key); Mount each function directly to the VM.

Source:

if (opts.data) {
            // Initialize data to get options.data and add them to listener
            initData(vm);
} else {
            // If value has an __ob__ instantiation dep object, add an __ob__ attribute to value.
            // Adding vm._data to the Observer returns the object instantiated by the new Observer
            observe(vm._data = {}, true /* asRootData */);

}

function initData(vm) {
        var data = vm.$options.data;
        // If data is a function, take the return of data, not its own value, and mount _data(object) with the value of data to Vue.
        data = vm._data = typeof data === 'function'
            ? getData(data, vm)
            : data || {};

        {money: 100, num: 12}
        if(! isPlainObject(data)) {// Reset data to {} if it is not an object and issue a warning log
            data = {};
            "development"! = ='production' && warn(
                'data functions should return an object:\n' +
                'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
                vm
            );
        }
        // example: ["money", "num", "arryList", "msgText"]
        var keys = Object.keys(data);
        var props = vm.$options.props; // Get the props attribute
        ƒ getMoreMoney()}
        var methods = vm.$options.methods; // Get the event
        var i = keys.length; // Get the length of the data key

        while (i--) { / / circulation data
            var key = keys[i];
            {
                if (methods && hasOwn(methods, key)) { // A warning is issued if the key in the data is the same as the key defined in the event
                    warn(
                        ("Method \"" + key + "\" has already been defined as a data property."), vm ); }}if (props && hasOwn(props, key)) { // Raises a warning if the key in the data is the same as the key defined in the props property
                "development"! = ='production' && warn(
                    "The data property \"" + key + "\" is already declared as a prop. " +
                    "Use prop default value instead.",
                    vm
                );
            } else if(! isReserved(key)) {// If the key does not start with $or _
                proxy(vm, "_data", key); // Set the listener to target, which is the key on each sourceKey on the Vue instance}}// Set the response for the data on the data object
        observe(data, true);
}
Copy the code

3-4-1. proxy

Function:

Set up the proxy, proxy properties on data and props directly to Vue, and set the GET and set methods

Source:

proxy(vm, "_props", key);
proxy(vm, "_data", key);

function proxy(
        target,  / / the Vue instance
        sourceKey, // Listen on key on Vue. Example: sourceKey = '_data'
        key / / example: msgText
    ) {
        debugger
        // Mount the get and set methods respectively
        sharedPropertyDefinition.get = function proxyGetter() { // Set the get function
            return this[sourceKey][key] // sourceKey = "_data", key = "num"
        };
        sharedPropertyDefinition.set = function proxySetter(val) {// Set the set function
            this[sourceKey][key] = val;
        };
        // Set the listener to target, which is the key on each sourceKey on the Vue instance
        /** * target = * {$attrs: (...) , $children: [], $createElement method: ƒ (a, b, c, d), $listeners: (...). . ƒ, msgText: ƒ getter (), get msgText: ƒ proxyGetter()
        Object.defineProperty(target, key, sharedPropertyDefinition);
}

var sharedPropertyDefinition = { // Share attribute definitions
        enumerable: true.configurable: true.get: noop,
        set: noop
};
Copy the code

After executing, mount money, num, and arryList from data directly to Vue instance.

3-5 observe

Function initData(vm) Observe (data, true) in the last step;

Function:

Creates an observer instance for an object, returning the existing observer instance if the object has already been observed, or creating a new observer instance otherwise

Execution process:

  • If data is not an object or an instantiated VNode, do nothing (non-objects and VNode instances do not respond) and exit the function
  • The first time it came in, there was nothing in value__ob__Of, if exist__ob__Property, which indicates that observations have been made and returns directly__ob__ Properties,ob = value.__ob__
  • If not, no.__ob__If value is an object and an array, create an observer instance,ob = new Observer(value);
  • If OB exists, the counter on OB is incremented
  • Finally return to OB

Source:

observe(data, true);

function observe(
        value, // The data object is passed in for the first time.
               // 例:{ arryList: [{…}],money: 100,msgText: "优秀的乃古:"num: 12 }
        asRootData //asRootData = true
    ) {
        debugger
        // Data is not an object or instantiated VNode
        if(! isObject(value) || valueinstanceof VNode) {
            return
        }
        var ob;
        // The first time it comes in, there is no __ob__ in value
        if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
            ob = value.__ob__;
        } else if (
            shouldObserve &&  / / shouldObserve is true! isServerRendering() &&// Not in the server node environment
            (Array.isArray(value) || isPlainObject(value)) && // Is an array or an object
            /** * Object. IsExtensible is used to determine whether objects can be extensible
            Object.isExtensible(value) && ! value._isVue/ / _isVue is false
        ) {
            // instantiate the dep object to add an __ob__ attribute to value
            ob = new Observer(value);
        }
        // If the value is RootData, that is, the value passed to data when we create a Vue instance, only RootData will count every time it observes Vue.
        // vmCount is used to count the number of times the Vue instance is used. For example, if we have a component logo, we need to display the logo in the header and tail of the page.
        // if this component is used, then vmCount is the number and the value is 2
        if (asRootData && ob) { // the root node data and ob exist
            ob.vmCount++; // Count the number of VMS
        }
        // Instantiate the dep object to add an __ob__ attribute to value
        // example: {dep: dep {id: 7, subs: Array(0)}, value: {__ob__: Observer}, vmCount: 0}
        return ob 
}
Copy the code

3-6 Observer

Function:

Observer constructor, for Observe.

The observer class, attached to each object being observed, adds an __ob__ attribute to value, and the attributes of the object are converted to getters/setters, collecting dependencies and notifying updates

Execution process:

  • Mount the current Observer to data’s __ob__
  • If value is an array, traverse each entry in the array and observe
  • If it is an object, iterate over each property and convert it to a getter/setter

Source:

ob = new Observer(value);

var Observer = function Observer(value) {
        this.value = value;
        this.dep = new Dep();
        this.vmCount = 0;
        // Set the listener value to be an object
        debugger
        // Add the __ob__ attribute to value, which is the Observer object, value.__ob__ = this;
        Each object in Vue.$data has an __ob__ attribute, including the Vue.$data object itself
        def(value, '__ob__'.this);
        debugger
        /** * value is array * hasProto = '__proto__' in {} * used to determine whether an object has a __proto__ attribute, __proto__ is not a standard property, so some browsers do not support it, such as IE6-10, Opera10.1, That's because we'll override the array's default seven prototype methods via a __proto__ prototype chain that manipulates data to implement array-responsive */
        if (Array.isArray(value)) { // Check whether it is an array
            var augment = hasProto  //__proto__ exists in all advanced browsers
                ? protoAugment
                : copyAugment;
            augment(value, arrayMethods, arrayKeys);
            this.observeArray(value); // Iterate over value, execute observe(items); Eventually, the walk method is executed
        } else {
            // Set responsiveness for each property of the object, including nested objects
            this.walk(value); }};// When the current browser supports' __proto__ ', the array API is mounted on the value prototype
function protoAugment(
    target, // 就是上面的value(当为数组格式时)
    src,  // Array API collection
    keys // The set of API keys will not be used here
    ) {
        target.__proto__ = src;
}

// If the current browser does not support '__proto__', set the proxy to value and mount the array method
function copyAugment(
    target, // 就是上面的value(当为数组格式时)
    src,  // Array API collection
    keys // Set of API keys
    ) {
        for (var i = 0, l = keys.length; i < l; i++) {
            varkey = keys[i]; def(target, key, src[key]); }}Copy the code

3-6-1 def

Function:

Define attributes with defineProperty, and override the key on the passed object as the passed function

Source:

  /** Define attributes with defineProperty. The first argument is the object, the second is the key, the third is the function, and the fourth is whether it can be enumerated. * /
    function def(
        obj, Obj = Array {}, Array prototype object
        key, // key = "push
        val, ƒ mutator()
        enumerable // enumerable = undefined
        ) {
        ƒ mutator(); // Obj
        / / such as: obj = {push: ƒ mutator (), __proto__ : Array (0)}
        Object.defineProperty(obj, key, { 
            value: val, / / value
            enumerable:!!!!! enumerable,// Defines whether the attributes of an object can be used in a for... Are enumerated in the in loop and object.keys ().
            writable: true.// You can rewrite value
            configurable: true  // The 6464x feature specifies whether the attributes of the object can be deleted without any additional writable information
            // Whether other features can be modified.
        });
    }
Copy the code

The def function has another function:

The def function is useful in several places. We know that Vue can respond to an array because we overwrote the array method, which looks like this:

    var arrayProto = Array.prototype;
    var arrayMethods = Object.create(arrayProto);

    // Overwrite the array methods
    var methodsToPatch = [
        'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
    ];

    /** * if the array interceptor method is used to update data, it will be called if the data is updated with 'push','pop','shift','unshift','splice','sort','reverse' methods
    methodsToPatch.forEach(function (
        method // The method name of the array
        ) {
        // Get the various native methods of the array
        var original = arrayProto[method];
        debugger
        def(arrayMethods, method, function mutator() {
            var args = [], len = arguments.length;
            while (len--) args[len] = arguments[len];
            var result = original.apply(this, args);
            var ob = this.__ob__;
            var inserted;
            switch (method) {
                case 'push':
                case 'unshift':
                    inserted = args;
                    break
                case 'splice':
                    inserted = args.slice(2);
                    break
            }
            if (inserted) {
                // Observe the array data
                ob.observeArray(inserted);
            }
            // Update notifications
            ob.dep.notify();
            return result
        });
    });
    
   DefineProperty defines attributes for the passed function with defineProperty. The first argument is object, the second is key, the third is vue, and the fourth is whether enumeration is possible. * /
    function def(
        obj, Obj = Array {}, Array prototype object
        key, // key = "push
        val, ƒ mutator()
        enumerable // enumerable = undefined
        ) {
            debugger
        ƒ mutator(); // Obj
        / / such as: obj = {push: ƒ mutator (), __proto__ : Array (0)}
        Object.defineProperty(obj, key, { 
            value: val, / / value
            enumerable:!!!!! enumerable,// Defines whether the attributes of an object can be used in a for... Are enumerated in the in loop and object.keys ().
            writable: true.// You can rewrite value
            configurable: true  // The 6464x feature specifies whether the attributes of the object can be deleted, and whether any features other than writable can be modified.
        });
    }

Copy the code

3-6-2 walk

Function:

Iterate over each property and convert them to getters/setters.

Source:

    Observer.prototype.walk = function walk(obj) {
        debugger
        var keys = Object.keys(obj); Keys = ["money", "num", "arryList"]
        for (var i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i]); // Pass each key to the object}};Copy the code

Let’s take a look at the defineReactive function, which is very long and will be explained in Section 4.

DefineReactive does:

  • Define a reactive property on object
  • Change the property key in obj to a getter/setter reactive property
  • Dependencies are collected in getters and fired in setters.

The 3-6-3 summary

Observe this function detects object changes by passing in an obj (the object whose change needs to be traced) and passing through defineReactive for each attribute. Add set and GET methods to each attribute. It’s worth noting that Observe makes recursive calls.

Observe The ob returned after the operation is complete:

{
    dep: {id: 2.subs: Array(0)},
    value: {
        "money": 100."num": 12."arryList": [{"name": "Subtree"}},vmCount: 1.__proto__: Object
}
Copy the code

Return to initData function observe(data, true)

At this point, the data format is already in the following format:

{
    arryList: [{ name: "Subtree"}].money: 100.num: 12.__ob__: Observer {value: {... },dep: Dep, vmCount: 1}, get arryList: ƒ reactiveGetter(), set arryList: ƒ reactiveSetter(newVal), get money: ƒ reactiveSetter(newVal), get num: ƒ reactiveGetter(), set num: ƒ reactiveSetter(newVal),__proto__: Object
}
Copy the code

Each property already has the corresponding GET and set methods mounted.

3-7. initComputed

Function:

Execution process:

  • Pass in a collection of computed properties
  • Create a new listener, Watchers, and mount it to _computedWatchers of the VM.
  • Define getters, taking itself if the evaluated property value is a function, and its GET property otherwise
  • If getter is empty Warning: Compute property missing getter
  • If not Node SSR render, add observer to Watchers calculated attribute
  • Determine the attribute key for computed
    • If not in the VM, define the computed properties and add the computed properties’ keys and computed functions directly to the VM (so we can use this.propsxx directly).
    • If in the VM, and in $data or props, caution: the computed properties are already defined in data or prop and cannot be repeated

Source:

if (opts.computed) {
       initComputed(vm, opts.computed);
}
        
function initComputed(
        vm, 
        computed // Example: {total: ƒ total()}
        ) {
        debugger
        // Create a new listener empty object and mount it to _computedWatchers of the VM
        var watchers = vm._computedWatchers = Object.create(null);
        // The server is rendered to determine whether it is a Node server environment
        var isSSR = isServerRendering(); 

        for (var key in computed) {
            var userDef = computed[key]; // get the calculation function
            // Define getters. If the evaluated property value is a function, take itself, otherwise take its GET property
            var getter = typeof userDef === 'function' ? userDef : userDef.get; 
            // If the getter is empty, the computed property is missing the getter
            if ("development"! = ='production' && getter == null) { 
                warn(
                    ("Getter is missing for computed property \"" + key + "\"."),
                    vm
                );
            }
            // If it is not Node SSR render, add observer to Watchers calculated attribute
            // This step shows that computed is also a watcher
            if(! isSSR) { watchers[key] =new Watcher(
                    vm, //vm vode
                    getter || noop,  //noop is empty
                    noop,  // The callback function
                    computedWatcherOptions  // The argument lazy = true
                );
                /** * watchers, example: * {total:{vm: Vue, deep: false, user: false, lazy: true, sync: false,... }} * /

            }
            if(! (keyin vm)) { 
                // If computed attribute key is not in the VM,
                // Define computed attributes and add computed attributes keys and computed functions to the VM
                defineComputed(vm, key, userDef); 
            } else {
                // Warning: calculated properties are already defined in data or prop and cannot be repeated
                if (key in vm.$data) {
                    warn(("The computed property \"" + key + "\" is already defined in data."), vm);
                } else if (vm.$options.props && key in vm.$options.props) {
                    warn(("The computed property \"" + key + "\" is already defined as a prop."), vm); }}}}Copy the code

3-7-1. defineComputed

Function:

Defines a calculated property and adds the property’s data to an object listener

Source:

function initComputed(vm, computed) {
        for (var key in computed) {
            var userDef = computed[key]; // get the calculation function
            if(! (keyin vm)) {
                // If computed attribute key is not in the VM,
                // Define computed attributes and add computed attributes keys and computed functions to the VMdefineComputed(vm, key, userDef); }}}function defineComputed(
        target, / / target
        key, //key
        userDef / / value
    ) {
        varshouldCache = ! isServerRendering();// If not node server is browser
        if (typeof userDef === 'function') { // Attribute value if it is a function
            sharedPropertyDefinition.get = shouldCache
                ? createComputedGetter(key) // If not node server is the browser to create calculated properties to get values to collect DEP dependencies
                : userDef; // The node server calls this function directly
            sharedPropertyDefinition.set = noop; // Assign an empty function
        } else {
            sharedPropertyDefinition.get = userDef.get ?// If userdef.get exists(shouldCache && userDef.cache ! = =false ? / / cache
                    createComputedGetter(key) :  // Create calculated properties to get values to collect DEP dependencies
                    userDef.get
                ) :
                noop;  // Give an empty function if userdef. get does not exist
            sharedPropertyDefinition.set = userDef.set // If userdef.set exists
                ? userDef.set
                : noop;
        }
        if ("development"! = ='production' &&
            sharedPropertyDefinition.set === noop) { // Warning if the value is equal to an empty function
            sharedPropertyDefinition.set = function () {
                warn(
                    ("Computed property \"" + key + "\" was assigned to but it has no setter."),
                    this
                );
            };
        }
        // Add object listener
        Object.defineProperty(target, key, sharedPropertyDefinition);
}

var sharedPropertyDefinition = { // Share attribute definitions
        enumerable: true.configurable: true.get: noop,
        set: noop
};


Copy the code

3-7-2. createComputedGetter

Function:

Create calculated properties to get values to collect DEP dependencies

Source:

function createComputedGetter(key) {
        // This method is called when the user values
        return function computedGetter() {
            // The object instantiated by Watcher
            var watcher = this._computedWatchers && this._computedWatchers[key];
            if (watcher) {
                if (watcher.dirty) {
                    // When dirty is true, it will be evaluated, where dirty is used as a cache
                    //this.value gets the value this.getter
                    watcher.evaluate(); 
                }
                if (Dep.target) {
                    // Add Watcher to watcher.newdeps.push (dep); A DEP object
                    // loop deps collect newDeps dep Collect dependencies again when newDeps data is empty
                    watcher.depend();
                }
                / / the return value
                return watcher.value
            }
        }
}
    
Watcher.prototype.evaluate = function evaluate() {
   this.value = this.get(); / / get the value
   this.dirty = false; // Slacker flag indicates that the value has been fetched once
};
Copy the code

3-8. initWatch

Function:

Iterate over watch, if the property value is an array, iterate over the property value (watch configuration item) to create watch

Source:

if(opts.watch && opts.watch ! == nativeWatch) {// Initialize Watch
     initWatch(vm, opts.watch);
}

// Initialize the Watch listener
function initWatch(
        vm, 
        watch // example: {money: {deep: true, immediate: true, handler: ƒ}}
        ) {
        debugger
        // Loop the watch object
        for (var key in watch) {
            var handler = watch[key]; // Get a single Watch configuration item
            // If it is an array handler
            if (Array.isArray(handler)) {
                // loop array creation listener
                for (var i = 0; i < handler.length; i++) {
                    createWatcher(
                        vm, // VM is a vUE object
                        key, //key
                        handler[i]// function or object); }}else {
                // loop array creation listener
                createWatcher(
                    vm, // VM is a vUE object
                    key, / / example: "money"
                    handler // Example: {deep: true, immediate: true, handler: ƒ}); }}}Copy the code

3-8-1. createWatcher

Function:

Iterate over watch, if the property value is an array, iterate over the property value (watch configuration item) to create watch

Execution process:

  • Handler if the watch configuration item is an object (in common format), get the execution function of the watch configuration item handler.
  • If the configuration item handler of Watch is a string, obtain the corresponding execution function directly from the VM.
  • $watch is used to execute the watch function based on immediate

Source:

// Escape the handler and create Watcher observers for the data
function createWatcher(
        vm,  / / vm object
        expOrFn, / / example: "money"
        handler, // Example: object corresponding to money in vue watch {deep: true, immediate: true, handler: ƒ}
        options  / / parameters
    ) {
        if (isPlainObject(handler)) {  // Determine if it is an object
            options = handler;
            handler = handler.handler; Handler. Handler is a function
        }
        if (typeof handler === 'string') { // Check whether handler is a string. If so, it is key
            handler = vm[handler]; // The value vm is the function in the outermost layer of Vue
        }
        // Escape the handler and create Watcher observers for the data
        return vm.$watch(
            expOrFn,// Key value or function
            handler, / / function
            options / / parameters
        )
        ƒ handler(newVal, oldVal), deep: true, depIds: Set(1) {3}, DEps: [Dep], dirty: false, expression: "money", getter: ƒ (obj), ID: 2, lazy: false, newDepIds: Set(0) {}, newDeps: [], sync: false, user: true, value: 100, vm: Vue {_uid: 0, _isVue: true, $options: {...}, _renderProxy: Proxy, _self: Vue,...}, *}] */
}
Copy the code

3-8-2. $watch

Function:

Whether to execute the watch function immediately according to immediate to uninstall the observer and remove the listener

Execution process:

  • If cb is an object, recursively execute createWatcher (which returns vm.$watch)
  • Set the user flag to true, and the user manually listens, which is the customized watch in options
  • Create a new Watcher
  • If the immediate attribute is true, the callback triggers the function immediately
  • Finally, execute watcher.teardown() to uninstall the listener

Associated documents:

If (options.immediate) {cb.call(vm, watcher.value); if (options.immediate) {cb.call(vm, watcher.value); }

Source:

// Execute before new Vue
function stateMixin(Vue) {... Vue.prototype.$watch =function (
            expOrFn, // Watch name, e.g. "money"
            cb, ƒ Handler (newVal, oldVal)
            options // Watch parameters: example: {deep: true, immediate: true, handler: ƒ}
        ) {
            debugger
            var vm = this;
            If it is an object, recursively deep listen until it is not an object
            if (isPlainObject(cb)) { 
                // Escape the handler and create Watcher observers for the data
                return createWatcher(
                    vm,
                    expOrFn,
                    cb,
                    options
                )
            }
            options = options || {};
            options.user = true; // The user manually listens, which is a customized watch in options
            var watcher = new Watcher(
                vm, 
                expOrFn, 
                cb,
                options 
            );
             ƒ Handler (newVal, oldVal), deep: true, depIds: Set(1) {3}, deps: [Dep], dirty: False, expression: "money", getter: ƒ (obj), ID: 2, lazy: false, newDepIds: Set(0) {}, newDeps: [], sync: false, user: True, value: 100, vm: Vue {_uid: 0, _isVue: true, $options: {... },... }, __proto__: Object } */
            // Immediate if the value of the attribute is true, the function is triggered immediately
            if (options.immediate) {
                cb.call(vm, watcher.value);
            }
            return function unwatchFn() { // Unmount the observer
                // Remove self from the subscriber list of all dependencies.watcher.teardown(); }}; }Copy the code

3-8-3. Watcher

Function:

The Watcher constructor

Execution process:

Source:

    var Watcher = function Watcher(
        vm, //vm dom
        expOrFn,  // Get the value of the function, or update the viwe attempt function
        cb, // The callback value is given to the callback function
        options, / / parameters
        isRenderWatcher// Whether to render the viewer
    ) {
        this.vm = vm;
        // Whether the viewer is already rendered
        if (isRenderWatcher) { // Assign the current Watcher object to vm._watcher
            vm._watcher = this;
        }
        The current Watcher is added to the vUE instance
        vm._watchers.push(this);
        // options
        if (options) { // If there are parameters
            this.deep = !! options.deep;/ / the actual
            this.user = !! options.user;/ / user
            this.lazy = !! options.lazy;// Lazy SSR rendering
            this.sync = !! options.sync;// If it is synchronous
        } else {

            this.deep = this.user = this.lazy = this.sync = false;
        }
        this.cb = cb; // The callback function
        this.id = ++uid$1; // uid for batching UID indicates the id of the batch listener
        this.active = true; / / activation
        this.dirty = this.lazy; // For lazy watchers
        this.deps = [];    // Observer queue
        this.newDeps = []; // New observer queue
        // An array object whose contents cannot be repeated
        this.depIds = new _Set();
        this.newDepIds = new _Set();
        // Make the function a string
        this.expression = expOrFn.toString();
        // Parse the expression for the getter
        if (typeof expOrFn === 'function') {
            // The function that gets the value
            this.getter = expOrFn;
        } else {
            // If it is a keepAlive component it will go here
            //path is a routing address
            // If (bailre.test (path)) {var bailRE = /[^\w.$]/; // Match true if not alphanumeric underscore $symbol
            // return
            // }

            // // does not match path in the split point
            // var segments = path.split('.');
            // return function (obj) {
            //
            // for (var i = 0; i < segments.length; i++) {
            // // returns true if there are arguments
            // if (! obj) {
            // return
            / /}
            // // assigns a key value to an object equal to segments using an array of dots as the key of obj
            // obj = obj[segments[i]];
            / /}
            // // otherwise returns an object
            // return obj
            // }

            // Match true if not alphanumeric underscore $symbol

            this.getter = parsePath(expOrFn);
            if (!this.getter) { // An empty array is given if none exists
                this.getter = function () {};"development"! = ='production' && warn(
                    "Failed watching path: \"" + expOrFn + "\" " +
                    'Watcher only accepts simple dot-delimited paths. ' +
                    'For full control, use a function instead.', vm ); }}this.value = this.lazy ?  // Lazy is true only if it is a component
            undefined :
            this.get(); // Compute getters and re-collect dependencies. Get the value
    };
Copy the code

3-8-4. Watcher.prototype.get

Function:

Compute getters and re-collect dependencies. To obtain the value value

Execution process:

Source:

    Watcher.prototype.get = function get() {
        // Add a dep target
        pushTarget(this);
        var value;
        var vm = this.vm;
        try {
            If an error is reported, catch is executed
            value = this.getter.call(vm, vm);
        } catch (e) {
            if (this.user) {
                handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\" "));
            } else {
                throw e
            }
        } finally {

            // "touch" every property so they are all tracked as
            // dependencies for deep watching
            // "touch" each property so that they are all traced as
            // Rely on deep observation
            if (this.deep) {

                // // if val has an __ob__ attribute
                // if (val.__ob__) {
                // var depId = val.__ob__.dep.id;
                // // Seen whether the depId attribute or method is available
                // if (seen.has(depId)) {
                // return
                / /}
                // // If no, add it
                // seen.add(depId);
                // }
                // Deeply collect the key from val for seenObjects
                traverse(value);
            }
            // Display a pushTarget
            popTarget();
            // Clean up the dependency collection.
            this.cleanupDeps();
        }
        / / the return value
        return value
    };
Copy the code

3-8-5. traverse

Source:

    function traverse(val) {
        // Add depId to seen
        / / seenObjects set object
        // Deeply collect the key from val for seenObjects

        _traverse(val, seenObjects);
        // Clear the object and empty it
        seenObjects.clear();
    }
    
        function _traverse(val, seen) {
        // de_traverse
        var i, keys;
        // Check if it is an array
        var isA = Array.isArray(val);
        The isFrozen method checks whether an object isFrozen.
        // Whether val is instantiated by VNode
        if((! isA && ! isObject(val)) ||Object.isFrozen(val) || val instanceof VNode) {
            return
        }
        // If val has an __ob__ attribute
        if (val.__ob__) {
            var depId = val.__ob__.dep.id;
            // Seen contains a depId attribute or method
            if (seen.has(depId)) {
                return
            }
            // Seen is seenObjects = new _Set(); Add is the add method in the set object
            // If not, add it
            seen.add(depId);
        }
        // If it is an array
        if (isA) {
            i = val.length;
            // then loop check the callback recursion
            while(i--) { _traverse(val[i], seen); }}else {

            keys = Object.keys(val);
            i = keys.length;
            // Loop recursively if it is an object
            while(i--) { _traverse(val[keys[i]], seen); }}}Copy the code

4. defineReactive

Object (obj, obj, obj, obj, obj, obj, obj, obj, obj, obj, obj, obj) ¶

Execution process:

  • Initialize a dependency object deP (e.g. {id: 3, subs: Array(0)}), a list of empty observers in the object to place dependencies that use this property in.
  • Exit the function if the corresponding key passed to the object cannot be modified
  • If the property has no get or set, and only two arguments are passed, set val as the value of the property passed in
  • If value has an __ob__ instantiation dep object, add an __ob__ attribute to the deP object
  • Setting properties with Object.defineProperty becomes getters and setters

Source:

Observer.prototype.walk = function walk(obj) {
        var keys = Object.keys(obj);
        for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); }};function defineReactive(
        obj, Obj = {money: 100, num: 12, arryList: Array(1), __ob__: Observer}
        key,// Object key key = "money"
        val, // Return data val = undefined
        customSetter, // Shallow = undefined
        shallow // Whether to add the __ob__ attribute shallow = undefined
    ) {
        
        // Initializes a dependency object with a list of empty observers to store all dependencies that use the property.
        var dep = new Dep(); // dep = Dep {id: 3, subs: Array(0)}
        / getOwnPropertyDescriptor methods: * * * * to determine whether this property key has its own properties, obj as the incoming Object, for example: * Object in getOwnPropertyDescriptor (obj, 'money'), the results are as follows:  * {value: 100, writable: true, enumerable: true, configurable: If the property can not be modified, the c64X works without any additional control system. If the property can not be modified, the c64X directly exits the function */
        var property = Object.getOwnPropertyDescriptor(obj, key);
        if (property && property.configurable === false) {
            return
        }

        // Get the implemented getter /setter methods
        var getter = property && property.get;
        var setter = property && property.set;
        
        // If the property has no get or set and only two arguments are passed, set val to the value of the property passed in
        // arguments is the entry to defineReactive. As you can see from the example, the other values are undefined
        // Only 2 arguments have values, so arguments are 2
        if((! getter || setter) &&arguments.length === 2) {
            val = obj[key]
        }
        debugger
        /** * Determine if value has an __ob__ instantiated dep object, get the deP object, add an __ob__ attribute to value * recursively add val to the Observer */ Return the instantiated object of the new Observer */
        varchildOb = ! shallow && observe(val);debugger
        console.log(Dep.target)
        // Properties become getters and setters
        Object.defineProperty(obj, key, {
            enumerable: true.configurable: true.get: function reactiveGetter() {
                // The value of the property is returned, and dependencies are collected to record where the property is used.
                /** * getter = property.get * To see if the property has a get method (e.g. 'money' has a get method via getOwnPropertyDescriptor) * If it does, call the get method * If it doesn't, Take the corresponding key */ on obj
                // 
                var value = getter ? getter.call(obj) : val;
                The dep. target global variable points to the Watcher generated by Complie, which is currently parsing the directive
                // Execute to dep.addSub(dep.target) to add Watcher to the Watcher list of the DEP object
                if (Dep.target) {  // dep. target Static flag indicates that the Dep has added an object instantiated by Watcher
                    // Add a dep
                    dep.depend(); Function depend() then function addDep(dep)
 
                    if (childOb) {  // Add a dep if the child node exists
                        childOb.dep.depend();
                        if (Array.isArray(value)) {  // Check if it is an array
                            dependArray(value);   // Add deP to the array}}}return value
            },
            set: function reactiveSetter(newVal) {
                /** * the new value, newVal, is assigned to the old val, if there is a previous setter, the old setter is used, if there is no setter, return. Finally, the new value adds an __ob__ attribute, which then triggers the dependency of that attribute, notifying them to change */
                var value = getter ? getter.call(obj) : val;
                /* eslint-disable no-self-compare */ will not be executed if the old and new values are the same
                if(newVal === value || (newVal ! == newVal && value ! == value)) {return
                }
                /* eslint-enable no-self-compare * when not in production * */
                if ("development"! = ='production' && customSetter) {
                    customSetter();
                }
                if (setter) {
                    The set method sets the new value
                    setter.call(obj, newVal);
                } else {
                    // The new value is given directly to him
                    val = newVal;
                }

                //observe Add an observerchildOb = ! shallow && observe(newVal);// If the data is reassigned, call Dep's notify method to notify all watchersdep.notify(); }}); }Copy the code

In fact, defineReactive has already been performed several times during initialization in New Vue, such as initRender, defineReactive

defineReactive(
                vm, / / the Vue instance
                '$attrs',
                parentData && parentData.attrs || emptyObject,  / / {}
                function () {
                    !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
                },
                true                
);
defineReactive(
                vm, '$listeners', 
                options._parentListeners || emptyObject, 
                function () {
                    !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
                }, 
                true
);
Copy the code

It mounts $attrs, $Listeners, and so on to Vue.

Let’s analyze the flow from Observe to defineReactive:

observe()=>new Observer=>function Observer()=>this.walk()=> defineReactive(obj, keys[i])=>function defineReactive()

At this point, the defineReactive is passed as an argument

    function defineReactive(
        obj, Obj = {money: 100, num: 12, arryList: Array(1), __ob__: Observer}
        key,// Object key key = "money"
        val, // Return data val = undefined
        customSetter, // Shallow = undefined
        shallow // Whether to add the __ob__ attribute shallow = undefined
    )
Copy the code

Shallow is empty, so var childOb =! shallow && observe(val); Observe (val). It goes back to the observe function, which was explained in the previous section. Note that val = obj[key] has been reset to 100.

function observe(
        value, / / 100
        asRootData // undefined
        ){
        // Value is not an object or an instantiated VNode
        if(! isObject(value) || valueinstanceof VNode) {
            return}}Copy the code

In this case, value does not meet the condition, and the childOb is still undefined.

Going back to defineReactive, it sets the get and set methods on the key of the passed object, and the properties become getters and setters.

4-1. Dep

Function: One DEP corresponds to one obj.key

The watcher is responsible for collecting dependencies for each DEP (or obj.key) dependency as it reads reactive data

Responsible for notifying the Watcher in the DEP to perform the update method during reactive data updates

Execution process:

  • Deps default to ids (make sure each Dep has a unique ID) and subs (an array to hold Watcher objects)
  • Several methods are mounted to the prototype:
    • AddSub: Adds a dependency Watcher to the subs array
    • RemoveSub: Removes dependencies
    • Depend: Sets a Watcher dependency
    • Notify: Notifies all Watcher objects to update the view
    • AddSub: Adds a dependency Watcher to the subs array

Source:

    var uid = 0;

    // Dep is the data dependency of subscriber Watcher, which loads all watches into its own subs array
    var Dep = function Dep() {
        // Each Dep has a unique ID
        this.id = uid++;
        /* An array of Watcher objects */
        this.subs = [];
    };
    
    // Add dependency Watcher to subs array
    Dep.prototype.addSub = function addSub(sub) {
        this.subs.push(sub);
    };
    
    // Remove dependencies
    Dep.prototype.removeSub = function removeSub(sub) {
        remove(this.subs, sub);
    };
    
    // Set a Watcher dependency
    // Dep. Target is a constructor call from Watcher
    Watcher's this.get call is not a normal call
    Dep.prototype.depend = function depend() {
        // Add a dep target Watcher dep is the deP object
        if (Dep.target) {
            // Add dependencies like directives
            Dep.target.addDep(this); }};/* Notify all Watcher objects to update the view */
    Dep.prototype.notify = function notify() {
        var subs = this.subs.slice();
        for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }}; Dep.target =null;
    // Target stack
    var targetStack = [];

    // Place the observer that deP exists in the targetStack for unified management, and then update dep. target to the incoming observer
    function pushTarget(_target) {
        //target is the Watcher dep object
        if (Dep.target) {
            targetStack.push(Dep.target);
        }
        Dep.target = _target;
    }
Copy the code

4-2. queueWatcher

Pushes observer to queue to filter duplicate ids except when * refreshed

    Watcher.prototype.update = function update() {
        // Watcher lazy defaults to false
        if (this.lazy) {
            this.dirty = true; // Lazy person flag
        } else if (this.sync) { // If it is synchronous
            // Update data
            this.run();
        } else {
            // If there are multiple observers
            queueWatcher(this); // The observer in the queue}};function queueWatcher(watcher) {
        debugger
        var id = watcher.id;
        //has is the object that records the observer's ID: {2: true}
        // If the watcher id passed in does not exist in HAS, it needs to be added to HAS
        // If it already exists, the current watch is a duplicate, exit the function directly
        if (has[id] == null) {
            has[id] = true;
            // The default is false, and the flushSchedulerQueue function waits for flags
            if(! flushing) { queue.push(watcher);Queue is an array of observer queues
            } else {
                // If it is refreshed, concatenate it according to the monitor id
                // If its ID is already passed, next will be run immediately.
                var i = queue.length - 1;
                while (i > index && queue[i].id > watcher.id) {
                    i--;
                }
                // Insert the concatenation into the array according to the id size
                queue.splice(i + 1.0, watcher);
            }
            // queue the flush
            if(! waiting) { waiting =true;
                // Collect the queue CB function for callbacks and whether to fire the callbacks queue function based on pending status
                debugger
                nextTick(
                    flushSchedulerQueue// Update observer Runs the observer watcher.run() function and calls the component's updated and activated hooks); }}}Copy the code

4-3. nextTick

Collect the queue CB function for callbacks and whether to fire the Callbacks queue function based on pending status

    function nextTick(cb, ctx) {
        debugger
        //cb callback function
        // CTX this points to
        var _resolve;
        // Add a callback to the queue
        callbacks.push(function () {
            if (cb) {
                // execute if cb exists and is a function
                try {
                    cb.call(ctx);
                } catch (e) {
                    // If it is not a function, an error is reported
                    handleError(e, ctx, 'nextTick'); }}else if (_resolve) {
                //_resolve If it exists_resolve(ctx); }});if(! pending) { pending =true;
            // Perform asynchronous macro tasks
            if (useMacroTask) {
                macroTimerFunc(); // Asynchronously trigger or implement observer trigger functions in the Callbacks queue
            } else {
                microTimerFunc(); // Asynchronously trigger or implement observer trigger functions in the Callbacks queue}}if(! cb &&typeof Promise! = ='undefined') {
            // Declare a Promise function if the callback does not exist
            return new Promise(function (resolve) { _resolve = resolve; }}})Copy the code

4-4. macroTimerFunc

Common Macro tasks in the browser environment include setTimeout, MessageChannel, postMessage, and setImmediate. Common Micro Tasks include MutationObsever and Promise.then. Macro Task implementation in Vue:

  • Preempt whether native setImmediate is supported, a feature only supported by advanced versions of IE and Edge
  • If not, check to see if native MessageChannel is supported.
  • If it is not supported, setTimeout 0 is degraded.
    var microTimerFunc; // Micro timer function
    var macroTimerFunc; // Macro timer function
    var useMacroTask = false; // Use macro tasks
    if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
        // Assign the function expression to macroTimerFunc
        macroTimerFunc = function () {
            debugger
            setImmediate(flushCallbacks);
        };
    } else if (typeofMessageChannel ! = ='undefined' && (
        isNative(MessageChannel) ||
        // PhantomJS
        MessageChannel.toString() === '[object MessageChannelConstructor]'
    )) {
       /** * creates a communication pipe with two ports, each of which can send data via postMessage, and one port can receive data from the other as long as it is bound to the onMessage callback method. * /
        var channel = new MessageChannel();
        var port = channel.port2;
        // Set the receiver function for port 1 to flushCallbacks
        channel.port1.onmessage = flushCallbacks;

        // Port 2 pushes information to port 1
        macroTimerFunc = function () {
            debugger
            port.postMessage(1);
        };
    } else {
        macroTimerFunc = function () {
            debugger
            setTimeout(flushCallbacks, 0);
        };
    }
Copy the code

Source to ask questions

1. Please explain the principle of reactive data

Vue2 — Core point: Object.defineProperty — Modifies each attribute

  • By default, when Vue initializes data, it uses Object.defineProperty for attributes in data, intercepts those acquired and set, and redefines all attributes.
  • Dependency collection (collecting the watcher for the current component) occurs when the page fetches the corresponding property.
  • If a property changes, the dependency is notified to update it.

Rely on the collection and distribution of updates:

Without this, every data update would render the page, which would be a huge performance drain. Add this operation to listen for related data changes, add to the queue, and render together when all changes are done.

Vue3 — Core point: Proxy — handles objects directly

Vue2 solved the problem of object recursion and array processing

Principle:

Responsive schematic diagram

2. How does vUE detect array changes?

Overrides array methods using function hijacking.

Vue rewrites the prototype chain of the array in data, pointing to the array prototype method defined by itself. This allows dependency updates to be notified when the array API is called. If the array contains a reference type, the reference type in the array is monitored again.

  1. Object.create(Array.prototype)copyArrayThe prototype chain is the new object;
  2. Intercepts the execution of the array’s seven methods and makes them responsive:push.pop.shift.unshift.splice.sort.reverse;
  3. Execute when the array calls each of the seven methodsob.dep.notify()Distribute noticesWatcherUpdate;

Principle:

For details, see [3-6-1 def].

3. Why does VUE use asynchronous rendering?

Vue is a component-level update, and if asynchronous updates are not used, the current component is re-rendered each time the data is updated. For performance reasons, VUE updates views asynchronously after this round of data updates.

Principle:

  • Dep. Notify: [3-6-1 def]
  • Subs [I].update(): [4-1 Dep]
  • QueueWatcher: [4-2 queueWatcher]
  • [4-3 nextTick]

Blog.csdn.net/weixin_4097…

4. How does nextTick work?

NextTick uses macro and micro tasks and defines an asynchronous method. Calling nextTick multiple times will queue the method, and the asynchronous method will clear the current queue, so nextTick is an asynchronous method.

5. Characteristics of computed data in VUE

Computed by default is also a Watcher, with a cache that updates the view only when the dependent properties change.

  • InitComputed: [3-7 initComputed]
  • DefineComputed: [3-7-1. DefineComputed]
  • CreateComputedGetter: [3-7-2 createComputedGetter]
  • [4-3 nextTick]

6. How is deep:true implemented in watch?

When the user sets the deep attribute in watch to true, if the monitored attribute is array type at that time, each item in the object will be evaluated, and the current Watcher will be stored in the dependency of the corresponding attribute, so that the data update will be notified when the object in the array changes. The internal principle is recursion, cost performance

  • initWatch
  • createWatcher
  • Vue.prototype.$watch
  • New Watcher: enclosing the get ();
  • Prototype. Get: if (this.deep) {// traverse(value)}
  • traverse

7. Why is data a function?

When the same component is reused multiple times, multiple instances are created. These instances use the same constructor, and if data is an object, all components share the same object. To ensure data independence, each component must return an object as its state through the data function.