This is the 22nd day of my participation in the August Wen Challenge.More challenges in August

MVVM

MVVM is a design pattern, or you can call it an architectural pattern. It stands for model-view-viewModel.

  • Model stands for Model
  • View stands for View
  • The ViewModel represents a controller

In this case, we can think of the Model as the data returned by the back-end interface

{name: 'sunny ', age:'18',
    mark: 'always 18'}Copy the code

A view is the page that the front end displays in the browser for the user to see

How is a piece of JSON data displayed on the page? That’s what the ViewModel is responsible for.

So, the relationship between them can be represented by a graph

The most common front-end FRAMEWORK for MVVM is probably

  • AngularJs, but this framework, seems to be less used in China
  • Especially bigvueWe often see this in vue demosvar vm = new Vue(...)The VM is shorthand for ViewModel.

Response principle

The original intention of MVVM is to use data binding functions to remove all code related to interface data rendering logic from the view level.

So how do you use or write data binding functions to achieve this effect?

Let’s look at vue’s performance:

<div id="counter">
  Counter: {{ counter }}
</div>

const Counter = {
  data() {
    return {
      counter: 0
    }
  }
}

Vue.createApp(Counter).mount('#counter')
Copy the code

Of course this example has nothing to do with Object.defineProperty, I’m just saying that “data binding removes code from the view level related to the interface data rendering logic”

Obviously, we didn’t write it ourselves

document.getElementById(counter).innerText = counter

or

$('#counter).text(counter)

Rendering code like this.

You might think this is just a line of code, that simple.

Actually, no, let’s say we want counter to increment

mounted() {
    setInterval(() = > {
      this.counter++
    }, 1000)}Copy the code

You can see how much effort MVVM saves us.

Ok, so now that I’ve shown you how MVVM behaves, how can I simply implement MVVM using Object.defineProperty?

In fact, the responsive principle of VUe2 is data hijacking, that is, when the data changes, the relevant page is automatically rerendered.

This requirement is pretty easy, but first understand a method called Object.defineProperty, and once you understand it most people can simulate a simple implementation.

It’s a far cry from a VUE, but the interview isn’t asking you to write a VUE.

DefineProperty data hijacking

The object.defineProperty method adds a new attribute to an Object or modifies an existing attribute of an Object before returning the Object.

The Object.defineProperty method can take three arguments

  • Object (Required, the object for which attributes are to be defined)
  • Propertyname (Required, name of the property to be defined or modified)
  • Descriptor (required, attribute descriptor to define or modify)
Object.defineProperty(object, propertyname, descriptor)
Copy the code

For Descriptor, it is an object type that is used to configure the property descriptor of the propertyName. Therefore, the property of Descriptor can be one of the following:

  1. Data descriptor
key Value types describe The default value
value any The value of the object. The propertyname undefined
writable boolean Whether object. Propertyname can be modified by the assignment operator false
configurable boolean Whether object. Propertyname can be modified or deleted false
enumerable boolean Whether object. Propertyname can be enumerated false
  1. Access descriptor
key Value types describe The default value
get function Function called when reading Object.propertyName undefined
set function Function called when setting Object. propertyName undefined
configurable boolean Whether object. Propertyname can be modified or deleted false
enumerable boolean Whether object. Propertyname can be enumerated false

A data descriptor that has no value, writable, GET, or set inside it is a data descriptor by default.

The following example shows the use of access descriptors:


let data = {}, temp = 'aa'

Object.defineProperty(data, 'key1', {
    set(value){
        console.log('this is a new value: ' + value)
        temp = value
        //some code like $('div').html(value) will automatic execute when key1 changed
    },
    get(){
        return temp
    }
})

data.key1 = 'Jack'
Copy the code

When we run the above code in the browser, the console will print:

this is a new value: Jack

This is where access descriptors come in. They can be used for data hijacking.

A subscription model

The subscription and data hijacking we are trying to implement is via the access descriptor of Object.defineProperty.

A subscriber can simply be thought of as a queue filled with functions that will be executed at some point in time.

And, of course, for the sake of finding it, we can define it as an object type, where each property is an array type.

var a = {a: [].b: [].c: []}Copy the code

Let’s implement a subscriber:

let Deep = {
  deepList: {},

  listen(key, fn){
    if(!this.deepList[key])
      this.deepList[key] = []
    
    this.deepList[key].push(fn)
  },

  trigger(){
    let key = Array.prototype.shift.call(arguments)

    let fnList = this.deepList[key]

    if(! key || ! fnList || ! fnList.length)return false
    
    for(let i=0, fn; fn = fnList[i++];) {
      fn.apply(this.arguments)
    }
  }
}
Copy the code

Bind the subscriber to data hijacking

The idea here is to execute the corresponding code in the subscriber once data hijacking occurs.

This way, you can implement vUe-like changes to a message, and the page can render the latest results synchronously.

The logic of this part of the code is very simple:

  • First, we bind the page tag to the content via deep.listen and put it into the subscriber
  • We then call the trigger method of the subscriber in a data hijack, updating the data and updating the HTML simultaneously
let dataHijack = ({data, tag, datakey, selector}) = > {
  let value = ' ', el = document.querySelector(selector);

  Object.defineProperty(data, datakey, {
    get(){
      return value
    },
    set(newVlaue){
      value = newVlaue
      Deep.trigger(tag, newVlaue)
    }
  })

  Deep.listen(tag, content= >{
    el.innerHTML = content
  })
}
Copy the code