【前端新手笔记19】浅析MVC

201 阅读4分钟

本文包含以下内容

  1. MVC是什么
  2. 为什么需要MVC
  3. MVC中包含的思维方式,包括最小知识原则、模块化、表思维等

MVC是什么

MVC(model view controller),具体含义没有严格定义,每个人都有自己的理解,M、V、C分别要做什么都没有统一,比如我们可以说

  • M - Model(数据视图)负责操作所有数据
  • V - View(视图)负责所有UI界面
  • C - Controller(控制器)负责其他,时联系model和view的桥梁,Controller拿到Model,然后将Model放到View里

MVC分别做什么(伪代码)⬇️

model{
	data:{这里是数据}
	create(){}
	delete(){}
	update(){}
	get(){}
}

view{
	el: {}
	html: {页面里的html}
	init(){}
	render(){}
}

controller{
	init()
	events:{}
	aotuBindEvents(){}
}

为什么需要MVC

  • 抛弃面条式代码,写代码更快
  • 让代码更易读
  • 提高代码拓展性、灵活性、可复用性,降低维护成本

MVC中体现的一些思维方式

思维方式1: 最小知识原则

引入的内容越少越好,比如将css和html都用js引入。可以利用模块化来实现,每个模块负责自己部分的html、css,自己照顾自己。这也是一个解耦的过程。

好处:

使用者可以直接引入,不需要管其他东西。 提高代码可读性。无论是自己,还是别人,都能快速理解。

代价:

比如如果将html放入js,一开始页面就是空白的,但是可以通过加入加载中状态来解决这个问题。

JS模块的一些基础知识

模块化的背景

当下,存在着运行大量JS脚本的复杂程序,模块化应运而生,模块化提供了一种将JS程序拆分为可按需导入的单独模块机制。

模块化的好处

提高项目的复用性、可读性、可维护性

模块的基本使用方法

使用 export default x 将一个变量默认导出给外部使用 使用 import x from './xxx.js' 引用另一个模块导出的默认变量 可以使用 import {x} from './xxx.js' 引用另一个模块导出的名为 x 的变量

思维方式2: 不变应万变

每个模块都可以用 m + v + c 的模式搞定,这是一个万金油的模式,不用每次都再思考如何写。

代价:

有时候会有一些多余的代码,但是多余的代码是为了再拓展项目的时候更加简单。 遇到特殊情况,mvc解决不了时需要一些变通,有时可能会麻烦。

思维方式3: 表驱动编程

当遇到多个大部分重复小部分不同的代码时,可以提炼成哈希表,然后去遍历表,使用表里的信息。这就是利用数据结构简化代码。表驱动将逻辑和数据分离。

好处:

逻辑和数据一目了然 数据来源可以非常灵活 降低了数据维护的成本和风险

思维方式4: 事不过三

同样的代码写过三遍,就应该抽成一个函数 同样的属性写三遍,就应该做成共同属性(原型或类) 同样的原型写三遍,就应该用继承

代价:

有的时候就会造成继承的层级过身,无法一下看懂代码,可以通过写文档、画类图解决

思维方式5: 俯瞰全局

当需要对象之间进行沟通时,包括单点对点单,单点到多点,多点到多点的通信,Event Bus 就是为了解决这种通信问题。

EventBus

EventBus 是一个用于组件间通信的框架。它为开发提供一种非常简便的方式来实现组件间解耦通信,并且提供了线程切换、优先级设置等功能,提高了开发的效率。

在MVC中使用 EventBus 可以满足最小知识原则,m 和 v 互相不知道对方的细节,但是却可以调用对方的功能

EventBus 常用的 API 有

  • .on(eventName, fn)监听事件
  • .trigger(eventName) 触发事件
  • .off(eventName, fn) 取消事件监听

例子👇

import './app1.css'
import $ from 'jquery'

const eventBus = $(window)
// 数据相关都放到m
const m = {
  data: {
    n: parseInt(localStorage.getItem('n'))
  },
  create() {},
  delete() {},
	//这里更新就用到了EventBus
  update(data) {
    Object.assign(m.data, data)
    eventBus.trigger('m:updated')
    localStorage.setItem('n', m.data.n)
  },
  get() {}
}
// 视图相关都放到v
const v = {
  el: null,
  html: `
  <div>
    <div class="output">
      <span id="number">{{n}}</span>
    </div>
    <div class="actions">
      <button id="add1">+1</button>
      <button id="minus1">-1</button>
      <button id="mul2">*2</button>
      <button id="divide2">÷2</button>
    </div>
  </div>
`,
  init(container) {
    v.el = $(container)
  },
  render(n) {
    if (v.el.children.length !== 0) v.el.empty()
    $(v.html.replace('{{n}}', n))
      .appendTo(v.el)
  }
}
// 其他都c
const c = {
  init(container) {
    v.init(container)
    v.render(m.data.n) // view = render(data)
		//这里一旦接收到update的事件,就会进行render
    c.autoBindEvents()
	    eventBus.on('m:updated', () => {
	      v.render(m.data.n)
    })
  },
  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)
    }
  }
}

export default c

思维方式6: view = render(data)

比起操作DOM,直接render会简单很多,我们只要改变data,就可以得到对应的view。

代价:

render粗犷的渲染肯定比DOM浪费性能,用到虚拟DOM可以解决这个问题。