VUE MVVM introduction and demonstration

MVVM introduction and demonstration

  • easyUI
  • knockout
  • smartclient
  • exit.js

During the interview, the interviewer will ask you to describe the MVVM you know?

What is MVVM implemented in Vue?

OK, let's solve the problem

  • First of all, the first M refers to the Model, that is, the * * data Model. In fact, it refers to the data * * in the Vue component instance. However, we have defined this data from the beginning, which is called responsive data

  • The second V refers to the View, that is, the * * page View. Changing to Vue is the DOM object converted from our template**

  • The third VM refers to * * ViewModel, which is the manager of View and data. It manages the work of changing our data into View. In Vue, it refers to our current Vue instance, a bridge between Model data and View view View communication**

  • In a word: data driven view, data change = > View update, bidirectional binding view update = > data change

Vue = = > MVVM = > bidirectional data binding = > this. Name = 'Zhang San'

React = > MVVM = > one way data binding = > only from data = > view = > this. Setstate ({Name: 'Zhang San'})

<!-- view -->
<template>
  <div>{{ message }}</div>
</template>
<script>
// Model common data object
export default {
  data () {
    return {
      message: 'Hello World'
    }
  }
}
</script>

<style>

</style>

MVVM responsive principle Object.defineProperty() - basic use

Next, we will focus on the principle and implementation of MVVM. Vuejs official website gives the principle and implementation of MVVM

Vue documentation

From the above documents, we can see that Vue's responsive principle (MVVM) is actually the following sentence:

When you pass a normal JavaScript object into the Vue instance as the data option, Vue will traverse all the properties of the object and use the Object.defineProperty Turn all these attributes into getter/setter . Object.defineProperty is a feature of ES5 that cannot be shim ed, which is why Vue does not support IE8 and earlier browsers.

From the above description, we found several keywords, object.defineproperty getter / setter

What is Object.defineProperty?

Definition: the Object.defineProperty() method will directly define a new property on an object, or modify an existing property of an object, and return the object.

Syntax: Object.defineProperty(obj, prop, descriptor)

Parameter: Obj = > the object on which to define the attribute.

Prop = > property name to be added or modified

Descriptor = > the attribute descriptor to be defined or modified.

Return value: the object passed to the function. That is, the obj object passed in

From the notes above, we can see what parameters need to be learned

obj is an object that can be new Object() or {}

prop is the property name, which is a string

What is the descriptor? What are the attributes

There are two main forms of attribute descriptors in object: data descriptors and access descriptors. A data descriptor is an attribute with a value that may or may not be writable. The access descriptor is a property described by the getter setter function. A descriptor must be one of these two forms; it cannot be both.

The above is the official description. It tells us that there are * * two patterns in the design of defineProterty, one is data description, the other is access description**

Descriptors must be one of the two. They cannot be both. That is to say, a mountain cannot have two tigers, neither can a mountain have two tigers

Let's write a simple example of * * data descriptor * *

        // Object.defineProperty(obj,prop, desciptor)
       //  Descriptor = > data descriptor access descriptor
       var obj = {
          name: 'Cao Yang'
       }
      var o = Object.defineProperty(obj, 'weight', {
           // Descriptor is an object
           // Data description access description
           value: '280kg'   // Data descriptor value
       })
       console.log(o)

Next, we will make a detailed analysis

Object.defineProperty() - data descriptor mode

What are the properties of the data descriptor?

  • Value = > the value corresponding to the attribute. Can be any valid JavaScript value (value, object, function, etc.). Unfind by default
  • Writable = > value can only be changed by the assignment operator if and only if the writable of the attribute is true. The default is false.

These two? Anything else?

  • Configuration = > when and only when the configuration of the attribute is true, the attribute descriptor can be changed, and the attribute can also be deleted from the corresponding object. The default is false. Decide whether writable can be changed
  • Enumerable = > the attribute can appear in the enumeration property of an object only if and only if the enumerable of the attribute is true. The default is false.

Why are configurable and enumerable different from value and writable?

Because these two attributes can appear not only in the data descriptor, but also in the access descriptor

We write a writable property and an unwritten property through the writeable and value properties

   var obj = {
          name: 'Cao Yang'
      }
     Object.defineProperty(obj, 'money', {
         value: "10k" // Salary is immutable at this time
     })
     Object.defineProperty(obj, 'weight', {
         value: '150 Jin', // Ten thousand hairs
         writable: true
     })
     obj.money = '20k'
     obj.hair = '200 Jin'
     console.log(obj)

Next, we want to make an immutable property variable

  var obj = {
          name: 'Cao Yang'
      }
     Object.defineProperty(obj, 'money', {
         value: '10k', // Salary is immutable at this time
         configurable: true  // The writeable property can only be changed if it is true here
     })
     Object.defineProperty(obj, 'weight', {
         value: '150 Jin', // Ten thousand hairs
         writable: true
     })
     obj.money = "20k"
     obj.weight = '200 Jin'
     console.log(obj)
     Object.defineProperty(obj, 'money', {
         writable: true 
     })
     obj.money = '20k'
     console.log(obj)

Next, we want to traverse to the two newly added attributes when traversing

      var obj = {
          name: 'Cao Yang'
      }
     Object.defineProperty(obj, 'money', {
         value: '10k', // Salary is immutable at this time
         configurable: true,
         enumerable: true
     })
     Object.defineProperty(obj, 'weight', {
         value: '150 Jin', // Ten thousand hairs
         writable: true,
         enumerable: true

     })
     obj.money = "20k"
     obj.weight = '200 Jin'
     console.log(obj)
     Object.defineProperty(obj, 'money', {
         writable: true 
     })
     obj.money = '20k'
     console.log(obj)
     for(var item in obj) {
         console.log(item)
     }

Object.defineProperty() - access descriptor mode

In the previous section, the unique attributes of data descriptors are value and writable, which means that in the access description mode

value and writable attributes cannot appear

What are the properties of the storage descriptor?

  • get a method that provides a getter for a property. If there is no getter, it is undefined. When the property is accessed, the method will be executed. No parameters will be passed in when the method is executed, but this object will be passed in (because of inheritance, this here is not necessarily the object defining the property).
  • set a method that provides a setter for a property. If there is no setter, it is undefined. This method is triggered when the property value is modified. The method takes a unique parameter, the new parameter value of the property.

get/set is actually our most common way to read values and set values

Call get method when reading value

Call set method when setting value

Let's make a way to read the settings through get and set

  var obj = {
          name: 'Cao Cao'
      }
      var wife = 'Little Joe'
      Object.defineProperty(obj, 'wife',{
          get () {
              return wife
          },
          set (value) {
             wife = value
          }
      })
      console.log(obj.wife)
     obj.wife= 'Big Joe'
      console.log(obj.wife)

However, what do we want to do with traversal? Note that when storing descriptors, they still have the configurable and enumerable properties,

Still configurable

 var obj = {
          name: 'Cao Cao'
      }
      var wife = 'Little Joe'
      Object.defineProperty(obj, 'wife',{
          configurable:true,
          enumerable: true,
          get () {
              return wife
          },
          set (value) {
             wife = value
          }
      })
      console.log(obj.wife)
     obj.wife= 'Big Joe'
      console.log(obj.wife)
      for(var item in obj) {
          console.log(item)
      }

Object.defineProperty() - simulate vm object

Through two sections, I learned the basic use of defineProperty. Next, we will simulate the effect of Vue instantiation through defineProperty

When Vue is instantiated, we explicitly assign data to data, but it can be accessed and set through * * vm instance. Property * *

How?

In fact, this is achieved through Object.defineProperty

 var data = {
        name: "Zhang San"
      };
      var vm = {};
      Object.defineProperty(vm, "name", {
        set(value) {
          data.name = value;
        },
        get() {
          return data.name;
        }
      });
      console.log(vm.name);
      vm.name = "Li Si";
      console.log(vm.name);

In the above code, we implement the data agent in vm, change the name in data directly to vm, that is, change the data

Summary: We proxy the data in data in the access descriptors of set and get,

MVVM = > data agent = > object. Defineproperty = > access descriptor get / set = > agent data

MVVM not only needs to acquire these data, but also update them to DOM in a responsive manner. In other words, when the data changes, we need to reflect the data * * to the view

Through debugging, we found that we can monitor the change of data in the set function. Only when the data changes, we need to notify the corresponding view to update

So how to notify? What technology to use? In the next section, we will bring the publish and subscribe mode

Introduction to the publish and subscribe mode

What is the publish and subscribe mode?

In fact, we have used it many times. Publishing / subscribing means that someone * * publishes news * * and someone subscribes to news. When it comes to data level, it means more = > more

That is, A program can trigger multiple messages or subscribe to multiple messages

We have used an eventBus in project 1 and project 2 of the black horse headline, which is the embodiment of the publish and subscribe mode

What do we do with this model?

In the last section, we have been able to capture the changes of data. Next, we will try to change our view by publishing and subscribing to this mode when the data changes

Let's write out some elements of the core code of this publish and subscribe

First, we want to get the publish / subscribe object through instantiation

Post $emit

Subscription $on

According to the above thought, we get the following code

 //  Create a constructor
      function Events (){}
        // Subscription message
        Events.prototype.$on = function(){}
        //  Publish news
        Events.prototype.$emit = function(){}

Implementation of publish and subscribe mode

    function Events () {
            this.subs = {}
        }
        Events.prototype.$on = function (eventName, fn) {
           this.subs[eventName] = this.subs[eventName]  || []
           this.subs[eventName].push(fn)
        }
        Events.prototype.$emit = function (eventName, ...params) {
            if(this.subs[eventName]) {
                this.subs[eventName].forEach(fn => {
                    //fn.apply(this, [...params])
                    //fn.call(this, ...params)
                    fn.bind(this,...params)()
                });
            }
        }
        var test = new Events()
        test.$on("updateABC", function(a,b,c){
          console.log(a+'-'+b+'-'+c)
          console.log(this)
        })
        var go = function(){
            test.$emit("updateABC", 1,2,3)
        }

Here, the call/apply/bind method is used to modify this point in the function

Using the publish and subscribe mode, you can realize that when an event is triggered, many people will be notified to do something, and the thing you do in Vue is to update the DOM

MVVM implementation DOM review

We have learned Object.defineProperty and publish / subscribe mode, and almost have the ability to write an MVVM,

But before implementing MVVM, let's review the meaning and structure of View, that is, Dom

What is DOM?

document object model

What is the role of Dom?

You can use * * object * * to manipulate page elements

What are the types of object nodes in Dom

You can check it with the following small example

  <div id="app">
        <h1>Our wills unite like a fortress,Common anti epidemic situation</h1>
        <div>
            <span style='color:red;font-weight: bold;'>fairly tall:</span>
            <span>Wish all students a bright future</span>
        </div>
    </div>
    <script>
       var app = document.getElementById("app")
       console.dir(app)
    </script>

From the above output, we can see that

Node type of element type nodeType is 1, text type is 3, and every content in document object is * * node**

childNodes is the child value of all nodes, all elements = > nodeType = > node type

All child nodes are placed under the property childNodes. ChildNodes is a pseudo array = > pseudo array has no array method and len gt h property

What is the attribute set of all tags?

attributes

What do we do to analyze DOM objects? The data capture and publish subscription we prepared earlier is to update dom

Let's start by writing an example of MVVM

MVVM implementation - Implementation of Vue's constructor and data broker

The challenge is coming. We need to write a simple * * vuejs * * to improve our own technical strength

We want to implement the constructor of mvvm

The constructor imitates vuejs and has data /el respectively

Data is finally proxied to the current vm instance, which can be accessed either through vm or through this.$data

        // First implement a constructor
        function Vue (options) {
            this.$options = options // All attributes give this an attribute
            this.$data = options.data || {}
            this.$el = typeof options.el ==="string"  ? document.querySelector(options.el) : options.el
            // Proxy all data to the current instance
            this.$proxyData()
        }
        // Proxy data
        Vue.prototype.$proxyData  = function () {
            Object.keys(this.$data).forEach(key => {
                Object.defineProperty(this, key, {
                    get () {
                        return this.$data[key]
                    },
                    set (value) {
                        if(this.$data[value] === value) return
                        this.$data[key] = value
                    }
                })
            })
        }
       var vm = new Vue({
          el: '#app',
          data: {
              name: 'Cao Yang',
              company: 'Seize the moon'
          }
        })
        console.log(vm.company)
        vm.company = 'Clasp the moon in the Ninth Heaven'
        console.log(vm.$data.company)
        vm.$data.company = 'Catch turtle in the sea'
        console.log(vm.company)

MVVM implementation - Data hijacking Observer

OK, the next step is very important. We need to do * * data hijacking * *. Who is Hijacking? Why hijacking?

In the code in the previous section, we can use vm.company = 'value' or vm.$data.name = 'value'. Where can we capture the change of data?

Whether this.data or this $data is changed to data data, so we need to * * hijack * * the data of data, that is, monitor its set

        // Monitoring data
        Vue.prototype.observer = function () {
           Object.keys(this.$data).forEach(key => {
            let value =  this.$data[key]
            Object.defineProperty(this.$data, key, {
                get () {
                    return value
                },
                set (newValue) {
                    if(newValue === value) return;
                    value = newValue
                    // If the data changes, we need to change the view
                }
            })
           })
        }

Hijack data in constructor

  // First implement a constructor
        function Vue (options) {
            this.$options = options // All attributes give this an attribute
            this.$data = options.data || {}
            this.$el = typeof options.el ==="string"  ? document.querySelector(options.el) : options.el
            // Proxy all data to the current instance
            this.$proxyData()
            this.observer() // Enable monitoring data
        }

MVVM implementation compile template Compiler design structure

Now we have basically implemented the instantiation of data and completed the hijacking of data. Next, we need to implement several methods

When the data changes = > convert the template to the latest object according to the latest data

Determine whether it is a text node

Determine whether it is an element node

Determine whether it is an instruction

Processing element nodes

Working with text nodes

So let's define the following methods

        // Compilation template
        Vue.prototype.compile = function () {}
        // Working with text nodes
        Vue.prototype.compileTextNode = function (node){}
        // Processing element nodes
        Vue.prototype.compileElementNode = function (node) {}
        // Determine whether it is a text node
        Vue.prototype.isTextNode = function(node) {};
        // Determine whether it is an element node
       Vue.prototype.isElementNode = function(node) {};
        // Determine whether the attribute is an instruction
        Vue.prototype.isDirective = function(attr) {};

MVVM implementation compile template Compiler implementation basic framework logic

We have got $el, the dom element of the page, through the constructor. Next, we can implement the basic logic of compilation

        // Compilation template
        Vue.prototype.compile = function (rootnode) {
            let nodes = Array.from(rootnode.childNodes) // First convert the pseudo array to an array
            nodes.forEach(item => {
                if(this.isTextNode(item)) {
                    this.compileTextNode(node)
                }
                if(this.isElementNode(item)) {
                    this.compileElementNode(node)
                    this.compile(node) // The idea of recursion
                }
            })
        }
        // Working with text nodes
        Vue.prototype.compileTextNode = function (node){}
        // Processing element nodes
        Vue.prototype.compileElementNode = function (node) {}
        // Determine whether it is a text node
        Vue.prototype.isTextNode = function(node) {
            return node.nodeType === 3;
        };
        // Determine whether it is an element node
       Vue.prototype.isElementNode = function(node) {
         return node.nodeType === 1;
       };
        // Judge whether the attribute is implemented
        Vue.prototype.isDirective = function(attr) {
            return attr.startsWith("v-");
        };

The basic logic of the above code is to use the method of text node when encountering text node to handle the method of element node when encountering element node

MVVM implementation compile template Compiler process text node

   // Working with text nodes
      Vue.prototype.compileTextNode = function (node){
            const text = node.textContent 
            const reg = /\{\{(.+?)\}\}/g
            if(reg.test(text)) {
                // If the double brace is satisfied
                const key = RegExp.$1.trim()
                this.$on(key, () => {
                    node.textContent = text.replace(reg, this[key]) // Replace the corresponding data if the brace is found
                })
                node.textContent = text.replace(reg, this[key]) // Replace the corresponding data if the brace is found
            }
        }

Tip: in actual development, regular is not required to be memorized but can be understood

MVVM implementation compile template Compiler process element node

   // Processing element nodes
        Vue.prototype.compileElementNode = function (node) {
          let atts = Array.from(node.attributes) 
          attrs.forEach(attr => {
              if(this.isDirective(attr.name)) {
                //  Determine whether it is an instruction
                if(attr.name === 'v-text') {
                    node.textContent = this[attr.value] // Equal to the value of the current property
                }
                if(attr.name === 'v-model') {
                    // v-model binds the value attribute of the form
                    node.value = this[attr.value]
                }
              }
          })
        }

MVVM implementation - data driven view - publish and subscribe Manager

At present, there are response data and compilation templates. We need to compile templates when the data changes

As mentioned before, this step needs to be done through publish and subscribe, so we implement publish and subscribe on the basis of Vue

        // First implement a constructor
        function Vue (options) {
            this.subs = {} //Publish Subscription Manager
            this.$options = options // All attributes give this an attribute
            this.$data = options.data || {}
            this.$el = typeof options.el ==="string"  ?                 document.querySelector(options.el) : options.el
            // Proxy all data to the current instance
            this.$proxyData()
            this.observer() // Enable monitoring data
        }
       Vue.prototype.$on=  function (eventName, fn) {
           this.subs[eventName] = this.subs[eventName]  || []
           this.subs[eventName].push(fn)
        }
        Vue.prototype.$emit = function (eventName, ...params) {
            if(this.subs[eventName]) {
                this.subs[eventName].forEach(fn => {
                    //fn.apply(this, [...params])
                    //fn.call(this, ...params)
                    fn.bind(this,...params)()
                });
            }
        }

MVVM implementation - driving view changes when data changes

Now everything is ready, only the east wind

Our data broker, data hijacking, template compilation, event publishing and subscription are all done. Now we only need to publish through events when the data changes, and then

Notify data to compile

     // Monitoring data
        Vue.prototype.observer = function () {
           Object.keys(this.$data).forEach(key => {
            let value =  this.$data[key]
            Object.defineProperty(this.$data, key, {
                get () {
                    return value
                },
                set: (newValue) => {
                    if(newValue === value) return;
                    value = newValue
                    
                    // If the data changes, we need to change the view
                    this.$emit(key) //Trigger data change
                }
            })
           })
        }

Change of monitoring data

  // Working with text nodes
        Vue.prototype.compileTextNode = function (node){
            const text = node.textContent 
            if(/\{\{(.+)\}\}/.test(text)) {
                // If the double brace is satisfied
                const key = RegExp.$1.trim()
                this.$on(key, () => {
                    node.textContent = text.replace(reg, this[key]) // Replace the corresponding data if the brace is found
                })
                node.textContent = text.replace(reg, this[key]) // Replace the corresponding data if the brace is found
            }
        }
        // Processing element nodes
        Vue.prototype.compileElementNode = function (node) {
          let atts = Array.from(node.attributes) 
          attrs.forEach(attr => {
              if(this.isDirective(attr.name)) {
                //  Determine whether it is an instruction
                if(attr.name === 'v-text') {
                    node.textContent = this[attr.value] // Equal to the value of the current property
                    this.$on(attr.value, () => {
                       node.textContent =  this[attr.value] // Replace the corresponding data if the brace is found
                   })
                }
                if(attr.name === 'v-model') {
                    // v-model binds the value attribute of the form
                    node.value = this[attr.value]
                    this.$on(attr.value, () => {
                       node.value =  this[attr.value] // Replace the corresponding data if the brace is found
                    })
                }
              }
          })
        }

Then we write an example to test it

MVVM implementation - view change update data

Finally, we hope to implement two-way binding, that is, when the view changes, the data changes at the same time

        // Processing element nodes
        Vue.prototype.compileElementNode = function (node) {
          let attrs = Array.from(node.attributes) 
          attrs.forEach(attr => {
              if(this.isDirective(attr.name)) {
                //  Determine whether it is an instruction
                if(attr.name === 'v-text') {
                    node.textContent = this[attr.value] // Equal to the value of the current property
                    this.$on(key, () => {
                       node.textContent =  this[attr.value] // Replace the corresponding data if the brace is found
                   })
                }
                if(attr.name === 'v-model') {
                    // v-model binds the value attribute of the form
                    node.value = this[attr.value]
                    this.$on(key, () => {
                       node.value =  this[attr.value] // Replace the corresponding data if the brace is found
                    })
                    node.oninput = () => {
                        this[attr.value] = node.value
                    }
                }
              }
          })
        }

Keywords: Front-end Vue Attribute Javascript React

Added by chooseodie on Fri, 14 Feb 2020 09:45:56 +0200