你的浏览器不支持canvas

Enjoy life!

javasript - 设计模式 - MVC模式

Date: Author: JM

本文章采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。

讲了那么多,我们应该怎么做了?请看以下的MVC模式。

一、MVC模式

relationship-map

  • MVC模式是软件工程中的架构模式:
    • 早期的开发中,MVC模式主要用于服务端,而前端主要负责View视图。
    • 随着BackboneJsEmberJs等框架的出现,前端也逐渐兴起了MVC模式

1.1 MVC的简介

  • MVC
    • M:模型层 – Model
      • 提供和保存业务数据。
      • 个人理解:模型层的代码主要是一些关于对数据处理的代码。
        • 例如:获取数据、对数据处理的一些代码、观察者模式的代码(下面的例子会讲到)
    • V:视图层 – View
      • 展示数据以及提供用户界面。
      • 个人理解:视图层的代码主要是一些展示页面的代码。
        • 例如:大量的DOM操作、一小部分用来更新视图的代码(这里与控制器层有关)
    • C:控制器层 – Controller
      • 处理业务的应用逻辑。
      • 个人理解:控制器层(Controller)视图层(View)模型层(Model)的桥梁 — 控制器层可以调用数据层数据和视图层内的视图创建页面增添逻辑。
        • 例:初始化模型层、视图层,通过控制层建立起视图层和模型层之间的联系

1.2 MVC模式整个过程的简单描述

  • 当用户与视图交互的时候,会触发一些用户的事件,这些事件会被控制器监听。
  • 控制器会根据不同的用户事件调用模块层的一些相应的接口,通过这个接口的调用,修改模块层的数据,导致模块层数据的改变。
  • 视图会根据观察者模式去观察模块的数据,当模块的数据进行改变的时候,则会通过事件通知的方式去通知视图
  • 最后,视图根据新的数据来改变自己的状态,即:改变我们的用户界面

二、实例1:简单的增减价钱牌实例

2.1 代码展示

relationship-map

  • html
<div class="calculator">
    <div class="num">0元</div>
    <button type="button" class="js-add">+</button>
    <button type="button" class="js-sub">-</button>
</div>
  • css
  .calculator {
            width: 300px;
        }
        .calculator .num {
            text-align: center;
            font-size: 80px;
            background: yellow;
        }
        .calculator button {
            width: 50%;
            font: bold 24px/38px '';
            float: left;
        }
  • js
  • Model
class Model {
    constructor () {
        this.val = 0;
        this.views = [];
    }
    
    // 增加
    add (num) {
        if (this.val <= 100) {
           this.val += num; 
        }
    }
    
    // 减少
    sub (num) {
        if (this.val > 0) {
            this.val -= num;
        }
    }
    
    // 获取值
    getVal () {
        return this.val;
    }
    
    // 注册
    register (view) {
        this.views.push(view);
    }
    
    // 通知
    notify () {
        this.views.forEach((view) => {
            view.render(this);
        })
    }
}
  • View
class View {
    constructor () {
        let doc = document;

        this.addBtn = doc.getElementsByClassName('js-add')[0];
        this.subBtn = doc.getElementsByClassName('js-sub')[0];
        this.num = doc.getElementsByClassName('num')[0];
    }
    
    // 事件绑定 
    addEvent (controller) {
        // 使用 bind 绑定this,经过修正后,this = controller,而不是按钮
        this.addBtn.onclick = this.controller.increase.bind(controller);
        this.subBtn.onclick = this.controller.descrease.bind(controller);
    }

    // 渲染
    render (model) {
        this.num.innerHTML = `${model.getVal()}元`;
    }
}
  • Controller
class Controller {
    constructor () {
        this.view = null;
        this.model = null;
    }

    init () {
        // 初始化Model 和 view
        this.model = new Model();
        this.view = new View(this);

        // 事件绑定
        this.view.addEvent(this.model);
    
        // View观察注册Model,当Model更新就会去通知View
        this.model.register(this.view);
        this.model.notify();
    }

    increase () {
        // 此时 this 指代的是 controller
        this.model.add(1);
        this.model.notify();
    }

    descrease () {
        // 此时 this 指代的是 controller
        this.model.sub(1);
        this.model.notify();
    }
}
  • 调用代码
var controller= new Controller();

controller.init();

2.2 遇到的问题

  • this的指向出了问题,代码如下:
class View {
    constructor (controller) {
        //...
    }
    // 一开始addEvent是这样写的
    addEvent () {
        this.addBtn.onclick = this.controller.increase;
        this.subBtn.onclick = this.controller.descrease;
    }
}

class Controller {
    // ...
    init () {
        // ...
        this.view.addEvent(this.model);
        // ...
    }

    increase () {
        // 此时 this 指代的是按钮addBtn
        // this.model = undefined
        // 因此会报错
        this.model.add(1);
        this.model.notify();
    }
    // ...
}
  • 我忽略了一句很重要的话:如果将类中定义方法提取出来单独使用,this会指向该方法运行时所在的环境。
    • 这就意味着 Controller类的increase拿到了View类的addEvent里使用,由于increase是绑定在按钮的click事件上, 即:increase是用在了addBtn这个对象上,因此,increase里的this的指向指代的是addBtn这个对象,所以, this.model = undefined
  • 解决方法:
    • bind:更改函数内this的指向,详情请看上面的代码。

对于本文内容有问题或建议的小伙伴,欢迎在文章底部留言交流讨论。