What’s the MVC

MVC is a well-known architectural pattern (design pattern).

How to design the structure of a program is a special discipline called architectural pattern, which belongs to the methodology of programming.

MVC, you divide your code into three modules, and you write it as three objects

  • The M-Model (data Model) is responsible for manipulating all data
  • V-view is responsible for all UI interfaces
  • C-controller is responsible for the others

There is no strict definition of MVC, and every programmer may have different understanding of MVC. The only unified thing is the cognition of M/V/C

The purpose of using the MVC pattern is simply to increase code flexibility and reuse by separating it.

MVC pseudo code

// Data layer, where operations on data are placed
const m = {
  data: { // Data initialization
    n: parseInt(localStorage.getItem('number') | |100)},update: function (data) { /* Update data */ },
  delete: function (data) { /* Delete data */ },
  get: function (data) { /* Get data */}}// View layer, where actions on views are placed
const v = { 
  el: 'Mount point (container)'.html: 'NEED to insert HTML content inside element'.render(data){ /* render HTML view */}}// The control layer, for event listening, goes here
const c = { 
  // Find important element binding events
  // If the event is triggered to call the change data method and render method
  a: $(Find a ' '),
  b: $('find b'),
  c: $('find c'),
  bindEvents: function(){   // bindEvents executes at render
    a.on('click'.function(){
      // Call the data layer method to change the data
      // Call the view-layer method to render the page
    })
    b.on('click'.function(){/ * * /})
    b.on('click'.function(){/ * * /}}})Copy the code

Why MVC

For people who “use simple, naive ideas (listening to events, changing DOM elements) to write code”, it might be more complicated to “implement something in an MVC way”

  • While there is nothing wrong with plain code logic, if the code volume increases, code with similar functions can become heavily duplicated, cumbersome to maintain later, and there is the potential for variable contamination. Such code has low reusability.
  • The process of applying MVC architecture is cumbersome and requires a lot of debugging, but the maintenance cost is low. Each piece of code is stored in a separate space as an object (module), responsible for a particular function, so it is easier to find the corresponding code, and changes within it will not have a big impact on the external code
// Find the important elements
const $button1 = $("#add1")
const $button2 = $("#minus1")
const $button3 = $("#mul2")
const $button4 = $("#divide2")
const $number = $("#number")
// Get data
const n = localStorage.getItem("n")
$number.text(n || 100)

// Listen for events to change data
$button1.on("click".() = > {  // 加1
  let n = parseInt($number.text())
  n += 1
  localStorage.setItem("n", n)
  $number.text(n)
})
$button2.on("click".() = > {  / / minus 1
  let n = parseInt($number.text())
  n -= 1
  localStorage.setItem("n", n)
  $number.text(n)
})
$button3.on("click".() = > {  / / by 2
  let n = parseInt($number.text())
  n *= 2
  localStorage.setItem("n", n)
  $number.text(n)
})
$button4.on("click".() = > {  // 除2
  let n = parseInt($number.text())
  n /= 2
  localStorage.setItem("n", n)
  $number.text(n)
})
Copy the code

modular

MDN: module

In the project to achieve “modularity”, in popular terms, is to create multiple JS files, the related functions of the code into the same JS file, so that modularity is achieved.

As the functionality of the application increases, the business logic becomes more complex, and so does the code. If all the functional codes are still put in a JS file, the codes of different functions are scattered and difficult to find and distinguish, and variable names may become very laborious, which ultimately leads to poor readability and reusability of the code and difficult to maintain in the later stage.

Therefore, in order to ensure that “the code can have a clear structure” and “it is convenient to find the code area corresponding to a certain function”, we split the code into different modules (files) according to different functions, so as to achieve “decoupling” between each module.

  • Decoupling: The code for each module exists independently and does not need to depend on other modules. You can even use Vue for one module, React for another, and jQuery for another. Just a little bigger)
  • Just like the building blocks we play with, the blocks can be combined together to form a shape, which can be split and replaced, because the blocks are independent and can be flexibly combined together as long as their interfaces (shapes) match. Decoupling is to gradually achieve this ideal state.

One criterion for partitioning modules is “high cohesion, low coupling”

  • High cohesion refers to the fact that a software module is composed of highly related code responsible for only one task, which is often referred to as the principle of single responsibility.
  • Low coupling refers to the connection between modules as little as possible, the interface as simple as possible, to achieve low coupling, thin line communication.
  • If the interface between each module is very complex, it indicates that the function division is unreasonable, the coupling between modules is too high, and the cohesion of individual modules is not high.

EventBus

communication

We talked about modularity above, and since we have divided each function into different modules (files), the problem is — what if file C detects user actions and needs to notify file M to modify the data, while FILE M needs to notify file V to render the data?

EventBus is used for communication between modules

  • EventBus is also a design pattern or framework for optimizing and simplifying communication between components/objects.
  • EventBus contains a number of methods. The ON method can listen for events, the trigger method can trigger events, and the off method can uninstall the listener
  • There is something similar to eventBus in both jQuery and Vue, called differently, but with the same function, communicating between components (modules).

Here is an example of eventBus generated with jQuery

/ / pseudo code
import $ from 'jquery'
const eventbus = $(window) // Return an object containing all the methods of eventBus

const model = { / / the data layer
  data: {'data':1},
  update(data){
    Object.assign(model.data,data) // Update data
    eventbus.trigger('Update data') // Trigger the event}}const view = {
  el: 'Mount point'.html: Content: '< div > {{}} < / div >'.init(container){
    view.el = $(container)
  },
  render(data){
    $(view.html.replace('{{n}}', n)).appendTo(view.el) // Replace the new (data) content, render into the page}}const control = {
  init(container){
    view.init(container)  // Get the mount point (element container)
    view.render(m.data.n) // Initialize the page
    autoBindEvents()
    eventbus.on('Update data'.() = > { 
      // Listen for eventbus.trigger in the data layer
      // If it is triggered, the data is updated for rendering
      view.render(m.data.n)
    })
  },
  add() { // Change the data
    m.update({n: m.data.n + 1})},minus() { // Change the data
    m.update({n: m.data.n - 1})},autoBindEvents() {  // Listen for buttons to change data
    view.el.on('click'.'app1'.'add')}}Copy the code

EventBus class

When the requirements are more complex (multiple application functions must use eventBus), we write eventBus as a separate class eventbus.js

Having generated instance objects inherit EventBus gives each instance the flexibility to trigger and listen

Follow the rule of “nothing but three”

  • If you write the same code three times, you should get a function
  • If you write the same property three times, you should make it a common property (stereotype or class).
  • If you write the same prototype three times, you should use inheritance

Cost: Sometimes the inheritance hierarchy is too deep to understand the code at once. It can be solved by writing documents and drawing class diagrams

// EventBus.js
import $ from "jquery"

class EventBus {
  constructor() {
    this._eventBus = $(window)}on(eventName, fn) {
    return this._eventBus.on(eventName, fn)
  }
  trigger(eventName, data) {
    return this._eventBus.trigger(eventName, data)
  }
  off(eventName, fn) {
    return this._eventBus.off(eventName, fn)
  }
}

export default EventBus
Copy the code
// app.js
import EventBus from "./base/EventBus.js"
const e = new EventBus()
e.trigger('xxx')          // Trigger the XXX event
e.on('xxx'.() = >{... })// Listen for the XXX event and execute the function
e.off('xxx')              // Delete the XXX event
Copy the code

Class inheritance

Follow the rule of “nothing but three”

  • If you write the same code three times, you should get a function
  • If you write the same property three times, you should make it a common property (stereotype or class)
  • If you write the same prototype three times, you should use inheritance.

Cost: Sometimes the inheritance hierarchy is too deep to understand the code at once. It can be solved by writing documents and drawing class diagrams

// Model.js
import EventBus from "./EventBus"
class Model extends EventBus {  / / extends 👈 👈 👈 👈 👈. }Copy the code
// app.js
import Model from "./base/Model.js"
const m = new Model()
console.log(m.trigger)
...
Copy the code

The Vue EventBus

Does Vue inherit from EventBus? A: there are

/ / verification
const v = new Vue({
  ...
})
console.dir(v)
// The first layer is the attribute given by Vue
$emit ($on); $emit ($once); $emit ($off);
// Start with $, all built-in Vue methods
Copy the code

Vue can also do eventBus

// const eventBus = $(window)
const eventBus = new Vue()  
console.log(eventBus.$on)
console.log(eventBus.$emit)
console.log(eventBus.$off)
Copy the code

view = render(data)

This thinking led to the birth of React

  • It’s much easier to render directly than to manipulate DOM objects
  • You just change the data, and you get the view

The price

Render coarse-expanded rendering definitely wastes more performance than DOM manipulation

  • For example, when the user switches to TAB 1, DOM operations directly locate the selected TAB and add a class to activate the TAB.

    However, the render thinking is to remove all the current element containers after data modification, and then re-render elements according to the new data, which is definitely more expensive than before

  • Of course, the cost of render can be offset by a “virtual DOM” that lets Render update only where it’s updated

    • When the “virtual DOM” is rendered, the first and second differences are compared and only the changes are rerendered

Graphic ⭕ ️

Contrast DOM manipulation with render thinking

  • Data flows from the right to the left, and then renders back to the right
  • Data always stays on the left and is eventually rendered to the right ✔️✔️✔️
    • The flow of data is more stable

case

Preliminary code 💩

Operating the DOM

const c = {
  init(container) {
    v.init(container)
    c.bindEvents() 
  },
  bindEvents() {
    v.el.on("click"."#add1".() = > {
      m.data.n += 1
      v.render()
    })
    v.el.on("click"."#minus1".() = > {
      m.data.n -= 1
      v.render()
    })
    v.el.on("click"."#mul2".() = > {
      m.data.n *= 2
      v.render()
    })
    v.el.on("click"."#divide2".() = > {
      m.data.n /= 2
      v.render()
    })
  }
}
export default c
Copy the code
// main.js
import x from "./app1.js"   // x is the address of c
x.init("#app1")
Copy the code
<! -- index.html -->
<body>
  <div class="page">
    <section id="app1"></section>
  </div>
  <script src="main.js"></script>
</body>
Copy the code

conversion

View = render(data)

const c = {
  init(container) {
    v.init(container)
    v.render(m.data.n) // view = render(data) first render
    c.autoBindEvents()
  },
  / /... .
  / /... .
  bindEvents() {
    // Event delegate
    v.el.on("click"."#add1".() = > {
      m.data.n += 1
      v.render(m.data.n) // view = render(data)
    })
    v.el.on("click"."#minus1".() = > {
      m.data.n -= 1
      v.render(m.data.n) // view = render(data)
    })
    v.el.on("click"."#mul2".() = > {
      m.data.n *= 2
      v.render(m.data.n) // view = render(data)
    })
    v.el.on("click"."#divide2".() = > {
      m.data.n /= 2
      v.render(m.data.n) // view = render(data)}}})Copy the code

Table driven programming

Table-Driven Methods (Table-Driven Methods) is a programming mode.

Application scenario: Eliminate frequent if else or switch case logical structure code, make code more simple

  • In fact, any information can be selected by the table. In simple cases, it is easier to use logical statements, but once the number of judgment conditions increases, it may be necessary to write a lot of repeated judgment statements. At this time, we realize the conditional judgment by traversing the table, which will get twice the result with half the effort.

Case 1

Requirement: Write a function that passes in the date and returns the corresponding day

  • Leap year meet :(four years a embellish and one hundred years not embellish) or (four hundred years embellish)

Regular writing

function getDay(year, month) {
  let isLeapYear = year % 4= = =0 && year % 100! = =0 || year % 400= = =0 ? 1 : 0
  if (month === 2) {
    return 28 + isLeapYear
  } else if (month===1||month===3||month===5||month===7||month===8||month===10||month===12) {
    return 31
  } else if (month === 4 || month === 6 || month === 9 || month === 11) {
    return 30}}console.log(getDay(2020.12))  / / 31
console.log(getDay(2020.2))   / / 29
console.log(getDay(2019.2))   / / 28
Copy the code

Table driven writing

const monthDays = [
  [31.28.31.30.31.30.31.31.30.31.30.31],
  [31.29.31.30.31.30.31.31.30.31.30.31]]function getDay(year, month){
  let isLeapYear = year % 4= = =0 && year % 100! = =0 || year % 400= = =0 ? 1 : 0
  return monthDays[isLeapYear][month-1]}console.log(getDay(2020.12)) / / 31
console.log(getDay(2020.2))  / / 29
console.log(getDay(2019.2))  / / 28
Copy the code

Case 2

Listen for element binding events

Regular writing

add1(){... }min1(){... }mul2(){... }div2(){... }document.querySelector('#add1').addEventListener('click', add1)
document.querySelector('#min1').addEventListener('click', min1)
document.querySelector('#mul2').addEventListener('click', mul2)
document.querySelector('#div2').addEventListener('click', div2)
document.Copy the code

Table driven writing

const controller = {
  add1(){},min1(){},mul2(){},div2(){},events: { // Table driver programming (object)
    "click #add1": "add1".// The first half of the key is the event to listen on, the second half is the element to listen on, and the value is the method to execute
    "click #min1": "min1"."click #mul2": "mul2"."click #div2": "div2"
  },
  autoBindEvents() {
    for(let key in this.events){ // Iterate over the object to get the corresponding key to perform the assignment
      const handler = this[this.events[key]]
      const [event, selector] = key.split("")  // ["click", "#min1"]
      $("Container").on(event, selector, handler) // Listen for events with extracted values}}})Copy the code

The “regular” code is simpler and straightforward, but it’s too repetitive. As the size of the data increases, so does the amount of code to write if there are 10 or 100 listening events

Table driven programming allows code to have a steady level of complexity, keeping it simple regardless of data size.

  • Avoid repetition and keep it simple. That’s what programmers are looking for

Refer to the article

Front-end MVC deformation: www.techug.com/post/mvc-de…

MVC is analysed