🌞

浅析 MVC

如果要说我目前在入门阶段遇到的几个让我最没自信的家伙,一个要数算法,然后就是 MVC 了。所以今天就简单写一下自己的一些思考。

可以先看一下 这篇文章

为什么要有设计模式?

Dry 原则:Don't Repeat Yourself

  • 代码级别的重复(相同的三行代码写了两遍)
  • 页面级别(类似的页面做了十遍)
  • 所有的页面都可以使用 MVC 来优化代码结构(万金油)

抽象思维

  • 最小知识原则
    • 引入一个模块需要 html css js
    • 引入一个模块需要 html js
    • 引入一个模块需要 js
    • 需要知道的知识越来越好,模块化为这一点奠定了基础
    • 代价:会让页面最开始是空白的,没有内容和样式
      • loading 图片、骨架、占位内容、SSR 技术(服务器端渲染)
  • 以不变应万变
    • 每一个模块都可以用 M + V + C 搞定,但有时候会多余一些用不到的代码,有时候遇到特殊情况不知道怎么变通
  • 表驱动编程
    • 当看到大批类似但不重复的代码,眯起眼睛,看哪些数据才是重要的
    • 重要的数据做成哈希表,代码会变简单
  • 事不过三
    • 同样的代码写了三遍,就抽象成函数
    • 同样属性写了三遍,就做成共用属性(原型或类)
    • 同样的原型写了三遍,就应该用继承
  • 俯瞰全局
    • eventBus
  • view = render(data)
    • 比起操作 DOM 对象,直接 render 简单多了,只要改变 data,就可以得到对应的 view
    • 数据永远从 JS(数据)流向页面(视图)
    • 代价:浪费性能
      • 虚拟 DOM 让 render 只更新该更新的地方
  • 解耦

MVC

MVC 是一种有名的设计模式,我们可以把一个模块用三个对象表示出来,这三个对象就是 M、V 和 C:

M - Model,数据模型,负责操作所有数据

V - View,视图,负责所有 UI 界面

C - Controller,控制器,负责其他

Model

我们可以用伪代码来尝试表示 Model 对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    M = {
      data: {
        key: value,
      },
      methods: {
        function Create: add data,
        function Remove: delete data,
        function Update: edit data,
        function Get: print data,
      }
    }

他是一个专门用来存放数据的对象,并且可以提供一系列对数据进行操纵的 API,比如 Create、Remove、Update、Get 等。

View

同样的,我们可以来表示一下 View 对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    V = {
      element,
      innerHTML,
      methods: {
        function Render: reload innerHTML,
        Operate: {
          function Create: create newElement,
          function Remove: remove innerElement,
          function Edit: edit innerElement,
          function Find: get innerElement,
        }
      }
    }

我们可以在 View 指定一个元素 element,可以对他内部的 HTML 代码进行修改和重载,同时也可以对其内部的元素进行增删改查等操作。

Control

对于 Control 部分的伪代码,我们也许可以这样写:

1
2
3
4
5
6
7
8
    C = {
      function Init: init M V C,
      function BindEvents: el.on('eventName', event),
      events: {
        function A: Update(data),
        function B: Render(html)
      }
    }

在 Control 中,我们可以完成初始化操作,可以为元素绑定事件,并书写事件方法,用来触发 M 或 V 的更改。

当然,对 MVC 的理解不同的人可能有不同的想法,作为初学者我只是简单理解了一下。比如有时候,我们对 C 的界定可能并不是那么明显——它既可以操作视图,又可以操作数据,那么为什么不把他就放在 V 或者 M 中呢?

EventBus

个人认为 EventBus 的出现主要是由于模块化和 MVC 的推进。前文也说了,在一个模块中,我们可以把所有的东西都分成 M V C 这三个对象来表示,这样虽然我们可以用统一形式的代码来表示几乎所有的情况,但同时也会带来一些问题。

首先,我们可以看看我们原来修改元素内容的方式:

1
2
3
4
5
    function X: 
      if event.status == triggered:
        data = element.getData();
        newData = data.edit();
        element.update(newData);

是一个非常自然的线性关系,当事件被触发时,直接修改 DOM 来改变元素内容,DOM 操作和数据是混在一起的,我们并没有在代码中单独保存数据。

而使用 MVC 之后,我们修改元素内容的方式变成了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    M = {
      data,
      function Update(data):
        edit data;
        eventBus.Trigger('data-updated')
    }
    
    V = {
      element,
      innerHTML,
      eventBus.On('data-updated', run Render(data))
      function Render(data): reload innerHTML
    }
    
    C = {
      function X:
        if event.status === triggered:
          M.Update(data)
    }

看起来稍稍有些复杂:

当事件被触发之后,C 中的事件绑定函数会调用 M.Update 方法来操纵 data,

但是负责视图的 V 此时并不知道事件已经触发,因此我们需要借助一个 “事件总线 EventBus” 来帮助我们完成事件从 M -> V 传递的这样一个过程,

M 更新完 data 之后触发 eventBus,

V 监听 eventBus,知道 M 已经更新完 data 了,然后调用 V.Render 方法更新视图。

当然除了帮助在 M V C 三个对象之间传递事件以外,eventBus 还可以帮助在模块之间传递事件。

下面用伪代码给出了一个 EventBus 常用 API 示例:

1
2
3
4
5
    EventBus: {
      function Trigger,
      function On,
      function Off
    }

表驱动编程

表驱动法,又称之为表驱动、表驱动方法。 “表”是几乎所有数据结构课本都要讨论的非常有用的数据结构。表驱动方法出于特定的目的来使用表,程序员们经常谈到“表驱动”方法,但是课本中却从未提到过什么是"表驱动"方法。表驱动方法是一种使你可以在表中查找信息,而不必用很多的逻辑语句( if 或 case )来把它们找出来的方法。事实上,任何信息都可以通过表来挑选。在简单的情况下,逻辑语句往往更简单而且更直接。但随着逻辑链的复杂,表就变得越来越富有吸引力了。

还是用伪代码来表示一下,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    Map = [
      key1: value1,
      key2: value2,
      key3: value3,
      key4: value4
    ]
    
    // 可以用上面这个表来简化我们的 if 语句,这样我们就不用写很多 if 了:
    
    if key in Map: Map[key]
    
    // 也可以用上面这个表来简化函数的声明等:
    
    for key in Map:
      function key: Map[key]

模块化

我对模块化的理解其实很简单:

首先,模块化可以把不同的功能分成不同的模块,模块之间的瓜葛要尽可能小,最好每个模块在不知道外部情况的时候也可以正常工作,这样我们之后维护代码只需要关注特定模块的部分即可;

其次,我们可以将 M V C 抽象为三个模块,做成 M 模块、V 模块、C 模块,这三个模块不需要关注具体的细节,只需要实现对应基本的、通用的功能,然后把 M V C 和每一个实现具体功能、操纵具体元素的模块分开,这样如果我们要对 M V C 进行统一的修改,就不需要深入每一个具体功能之中了。

updatedupdated2020-02-122020-02-12