What is MVC?

M is model, namely data model, responsible for data-related tasks, including adding, deleting, modifying and checking data. V is the view, the view layer, the interface that the user can see. C is the Controller, the Controller that listens for user events and then calls M and V to update data and views.

Recently I learned about the MVC design pattern and found that there is no clear approach to MVC except the above definition. MVC is actually an abstract concept that changes code into structure.

1.1 Model Data Model

/ / sample
let Model = {
  data: {data source},create: {add data},delete: {delete data},update(data) {
    Object.assign(m.data, data); // Replace old data with new data
    eventBus.trigger("m:update"); //eventBus triggers the 'M :update' message, notifying the View to refresh the interface
  },
  get: {get data},};Copy the code

1.2 View View layer

/ / sample
let View={
    el: The element to refresh, HTML:'Refresh content to display on the page'
    init(){v.l: Initialize the element to be refreshed},render(){refresh page}}Copy the code

1.3 Controller Controller

By binding events, the controller calls M and V to update data and views based on user actions

let Controller={
    init(){
        v.init()// Initialize the View
        v.render()// Render the page for the first time
        c.autoBindEvents()// Automatic event binding
        eventBus.on('m:update'.() = >{v.render()}// When enentsBus triggers 'm:update', the View is refreshed},events:{events are stored as hash table records},/ / such as:
   events: {
    'click #add1': 'add'.'click #minus1': 'minus'.'click #mul2': 'mul'.'click #divide2': 'div',},add() {
      m.update({n: m.data.n + 1})},minus() {
      m.update({n: m.data.n - 1})},mul() {
      m.update({n: m.data.n * 2})},div() {
      m.update({n: m.data.n / 2})},method(){data= new data m.pdate (data)// Controller notifies model to update data
    },
    autoBindEvents(){
    	for (let key in c.events) { // Iterate through the Events table and automatically bind events
      const value = c[c.events[key]]
      const spaceIndex = key.indexOf(' ')
      const part1 = key.slice(0, spaceIndex) / / get a 'click'
      const part2 = key.slice(spaceIndex + 1)  / / get '# add1'
      v.el.on(part1, part2, value)
    }
}
Copy the code

1.4 the MVC instance

Goal: Make an addition, subtraction, multiplication and division calculator

The initial app1.js file is as follows:

import "./app1.css";
import $ from "jquery";

// Initialize HTML
const html = ` 
      
`
; const $element = $(html).appendTo($("body")); // Initialize the data const $number = $("#number"); let number = parseInt(localStorage.getItem("number")); // Render data to the page $number.text(number || 100); let n = parseInt($number.text()); // Find the element to operate on const $add = $("#add"); const $minus = $("#minus"); const $mul = $("#mul"); const $divide = $("#divide"); // Bind mouse events $add.on("click".() = > { n += 1; $number.text(n); localStorage.setItem("number", n); }); $minus.on("click".() = > { n -= 1; $number.text(n); localStorage.setItem("number", n); }); $mul.on("click".() = > { n *= 2; $number.text(n); localStorage.setItem("number", n); }); $divide.on("click".() = > { n /= 2; $number.text(n); localStorage.setItem("number", n); }); Copy the code

But the whole code is not that structured, we can put all data related objects in M, view related objects in V, everything else in C, the idea is to initialize n => render => update n => render again

The app1.js code is as follows:

import "./app1.css";
import $ from "jquery";
/ / m data
const m = {
  data: {
    n: parseInt(localStorage.getItem("number")),}};/ / v view
const v = {
  el: null.html: ` 
      
{{n}}
`
.init(el) { v.el = $(el); v.render(); }, // view = render(data) render(n) { if(v.el.children.length ! = =0) v.el.empty(); $(v.html.replace("{{n}}", n)).appendTo(v.el); }};// c controller const c = { init(el) { v.init(el); v.render(m.data.n); c.bindEvents(); }, // Bind the mouse event delegate bindEvents() { v.el.on("click"."#add".() = > { m.data.n += 1; localStorage.setItem("number", m.data.n); v.render(m.data.n); }); v.el.on("click"."#minus".() = > { m.data.n -= 1; localStorage.setItem("number", m.data.n); v.render(m.data.n); }); v.el.on("click"."#mul".() = > { m.data.n *= 2; localStorage.setItem("number", m.data.n); v.render(m.data.n); }); v.el.on("click"."#divide".() = > { m.data.n /= 2; localStorage.setItem("number", m.data.n); v.render(m.data.n); }); }};export default c; Copy the code

Second, the EventBus

2.1 What is EventBus?

EventBus is mainly used for communication between objects. For example, in the above example, the Model data Model and the View Model are unaware of each other’s existence but need to communicate, so we use EventBus summary: The principle of minimum knowledge can be met with eventBus, where M and V do not know each other’s details but can call each other’s functions

2.2 What apis does EventBus have?

EventBus provides apis such as ON, off, and trigger. On is used to listen for events and trigger is used to trigger events. For example, in the MVC model above, when the M data model is updated, trigger triggers an event

const m = {
  ....
  update(data) {
    Object.assign(m.data, data)
    eventBus.trigger('m:updated')  // Inform the View layer that I have updated the data and the view should start working
    localStorage.setItem('n', m.data.n)
  },
  ....
}
Copy the code

Then in controller, the Controller listens for events with ON and tells the View model to re-render the page

const c = {
  init(container) {
    v.init(container)
    v.render(m.data.n) // view = render(data)
    c.autoBindEvents()
    eventBus.on('m:updated'.() = > {   // Controller will use on to listen for events,
      // Then inform the View model to re-render the page
      console.log('here')
      v.render(m.data.n)
    })
  },
  ...
}
Copy the code

Table driver programming

When we need to judge more than three cases and make corresponding things, we often need to write a lot of If and else, which is not readable enough. In order to improve the readability of the code, we can use table-driven programming, save the value used to judge the If condition into a hash table, and then take an example from the table: In the example above, we add, subtract, multiply and divide the four buttons to determine which one was clicked and then modify the output value. Traditionally, we would bind the click event to each of the four buttons and then write the four callback functions to modify the value

$button1.on('click'.() = > {
    let n = parseInt($number.text())
    n += 1
    localStorage.setItem('n', n)
    $number.text(n)
})

$button2.on('click'.() = > {
    let n = parseInt($number.text())
    n -= 1
    localStorage.setItem('n', n)
    $number.text(n)
})

$button3.on('click'.() = > {
    let n = parseInt($number.text())
    n = n * 2
    localStorage.setItem('n', n)
    $number.text(n)
})

$button4.on('click'.() = > {
    let n = parseInt($number.text())
    n = n/2
    localStorage.setItem('n', n) $number.text(n)}) -------- after the event delegate -------const c = {
    init(container) {
        v.init(container)
        v.render(m.data.n)
        c.BindEvents()
    }
    BindEvents() {
        v.el.on('click'.'#add1'.() = > {
            m.data.n += 1
            v.render(m.data.n)
        })
        v.el.on('click'.'#minus1'.() = > {
            m.data.n -= 1
            v.render(m.data.n)
        })
        v.el.on('click'.'#mul2'.() = > {
            m.data.n *= 2
            v.render(m.data.n)
        })
        v.el.on('click'.'#divide2'.() = > {
            m.data.n /= 2
            v.render(m.data.n)
        })
    }
}
Copy the code

1. Bind the parent element of the add, subtract, multiply, and divide buttons, using only one event listener 2. Use hash tables to store buttons and their corresponding operations

const c = {
  events: {
    'click #add1': 'add'.'click #minus1': 'minus'.'click #mul2': 'mul'.'click #divide2': 'div',},add() {
    m.update({n: m.data.n + 1})},minus() {
    m.update({n: m.data.n - 1})},mul() {
    m.update({n: m.data.n * 2})},div() {
    m.update({n: m.data.n / 2})},autoBindEvents() {
    for (let key in c.events) {
      const value = c[c.events[key]]
      const spaceIndex = key.indexOf(' ')
      const part1 = key.slice(0, spaceIndex)
      const part2 = key.slice(spaceIndex + 1)
      v.el.on(part1, part2, value)
    }
  }
Copy the code

Four, modular

Modularity is taking relatively independent code out of a large chunk of code and breaking it down into smaller, smaller modules that are relatively independent of each other, The syntax of ES6 includes Import and export to implement modularity when we package the Controller model in app1.js and export the Controller:

export default c; // Export by default
export { c }; // Another way to export. Remember to put curly braces
Copy the code

In main.js we want to use controller:

import x from './app1.js'Is equivalent toimport {default as x} from './app1.js'

x.init('#app1')
Copy the code

More examples of rename exports:

// inside module.mjs
export { function1, function2 };

// inside main.mjs
import {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName,
} from "/modules/module.mjs";
Copy the code

Five, the summary

5.1 Minimum knowledge principle

  • Use a module with minimal knowledge, usually we need to introduce HTML, and in HTML CSS and JS, now only need to introduce a module JS is enough

  • When there are multiple modules. Import the js of each module through main.js as an entry file, and don’t worry about the HTML and CSS of each module, because JS takes care of itself. There is a problem: when the network speed is relatively slow, it needs to wait for the completion of loading JS to display the content, and a blank screen appears. You can add chrysanthemums and skeletons to improve user experience

    5.2 m + v + c

  • From this evolution process, although MVC is finally condensed into a V in VUE, in fact, VUE still has the idea of M (data), V (Template) and C (methods), and each module can follow this mode.

    5.3 Abstract repetition

  • Same code repetition: abstracted as a function

  • The same property repeats: abstracted as a stereotype or class

  • Same stereotype repetition: use inheritance