MVC和重构

MVC 是什么

  • M-Model (数据模型)

负责操作所有数据

  • V-View (视图)

负责所有 UI 界面

  • C-Controller(控制器)

负责其他

// 数据放在m
const m = {
  data: {},
  // 对数据增删改查
  create() {},
  delete() {},
  update(data) {},
  get() {},
};
// 视图放在v
const v = {
  el: null,
  html: ``,
  init(container) {
    v.el = $(container);
  },
  // 渲染函数,参数将是数据。视图全都是对数据渲染 view = render(data)
  render(x) {},
};
// 其他放在c
const c = {
  init(container) {},
  events: {},
  add() {},
  minus() {},
  mul() {},
  div() {},
  // 自动绑定事件
  autoBindEvents() {},
};

代码重构

最小知识原则

  • 引入一个模块需要引入 html、css、 js
  • 引入一个模块需要引入 html、js
  • 引入一个模块需要引入 js
  • 你需要知道的知识越少越好,模块化为这一点奠定了基础

在 ES6 中,可以使用 import 关键字引入模块,通过 export 关键字导出模块

表驱动编程: 自动绑定事件

  • 利用遍历哈希表,把元素绑定上事件
//自动绑定事件
  // 把所有事件写成哈希表
  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]]; // 要执行的函数  如add
      const spaceIndex = key.indexOf(" ");
      const part1 = key.slice(0, spaceIndex); // 事件名part1  如click
      const part2 = key.slice(spaceIndex + 1); // 实际监听元素part2   如#add1元素
      v.el.on(part1, part2, value);
    }
  }

事不过三

  • 同样的代码写三遍,就应该抽成一个函数

如表驱动编程的自动绑定事件和下面的例子

class Model {
  constructor(options) {
    const keys = ['data', 'update', 'create', 'delete', 'get']
    keys.forEach((key) => {
      if (key in options) {
        this[key] = options[key]
      }
    })
  }
  update(){}
  create(){}
  delete(){}
  get(){}
  }

// 烂代码
class Model {
  constructor(options) {
  this.data = option.data
  this.update = option.update
  this.create = option.create
  this.delete = option.delete
  this.get = option.delete

// 这里没有超过三个,就不用遍历,写成解构形式
class View {
    constructor ({el,html,render}){
        this.el = $(el)
        this.html = html
        this.render = render
    }
}
  • 同样的属性写三遍,就应该做成共用属性(原型或类)

如 MVC 变成 Model 类、View 类

// 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;
// Model.js
import EventBus from "./EventBus";

class Model extends EventBus {
  constructor(options) {
    super();
    const keys = ["data", "update", "create", "delete", "get"];
    keys.forEach((key) => {
      if (key in options) {
        this[key] = options[key];
      }
    });
  }

  create() {
    console && console.error && console.error("你还没有实现 create");
  }

  delete() {
    console && console.error && console.error("你还没有实现 delete");
  }

  update() {
    console && console.error && console.error("你还没有实现 update");
  }

  get() {
    console && console.error && console.error("你还没有实现 get");
  }
}
export default Model;

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

俯瞰全局

  • 利用eventBus
// 旧代码
const eventBus = $(window);
eventBus.trigger("m:updated");
eventBus.on("m:updated", () => {
  // 监听m:updated事件,每次触发就重新用新数据渲染一遍
  v.render(m.data.n);
});

现在,让所有类都继承 EventBus 类,那每个类的具体的对象就可以直接使用.on, .off,.trigger

view = render(data)

  • 视图是对数据的渲染。

每次数据改变就重新渲染一次

  • 比起操作 DOM 对象,直接 render 简单多了
  • 只要改变 data,就可以得到对应的 view
render(x) {
    if (v.el.children.length !== 0) v.el.empty();
    // 把html里的占位符替换成x,再加入容器中
    $(v.html.replace("{{n}}", x)).appendTo(v.el);
  }
};

vue不是这样,但是react是这样

代价:render 粗犷的渲染肯定比 DOM 操作浪费性能,这里其实最好用虚拟 DOM ,虚拟 DOM 能让 render 只更新该更新的地方

代码示例:

mvc-demo-1

mvc-demo-2

mvc-demo-3

扩展阅读: 前端 MVC 变形记 | EFE Tech

comments powered by Disqus