前端MVC/MVP/MVVM示例

前言
long long ago,我是做iOS的,当时使用MVC做开发。之后借助学习Android的机会认识了MVP模式,最后又因为各种阴差阳错接触了MVVM。由于后两者接触的时间不长,所以对他们始终是有点一知半解,借此机会系统的整理一下,顺便做下记录。
阅读目标

  1. 前端使用MVC
  2. 前端使用MVP
  3. 前端实现MVVM

MVC

ModalView不可直接通信,通过Controller作为他们之间的桥梁,如下:

  1. 在Controller发送请求或响应事件,获取Modal
  2. 在Controller将Modal发送给View从而更新UI

index.html

<html>
  <script src='./mvc/Container.js'></script>
  <script src='./mvc/View.js'></script>
  <script src='./mvc/Modal.js'></script>

  <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
  <body>
    <h1 id='num'></h1>
    <button id='increase'>增加</button>
    <button id='decrease'>减小</button>
  </body>
</html>
<script>

// ##################### MVC #######################
   window.onload = function () {
     // MVC的入口一般是Controller层
     let controller = new MyApp.Controller();
     controller.init();
   }

</script>

Container.js

const MyApp = {}; // 创建这个应用对象

MyApp.Controller = function() {
  let modal = null;
  let view = null;

  this.init = function() {
      /* 初始化Modal和View */
      modal = new MyApp.Modal();
      view = new MyApp.View(this);
      view.render(modal);
  };

  /* 让Modal更新数值并通知View更新视图 */
  this.increase = function() {
      modal.add(1);
      view.render(modal);
  };

  this.decrease = function() {
      modal.sub(1);
      view.render(modal);
  };
};

Modal.js

MyApp.Modal = function() {
  let val = 0;

  this.add = function(v) {
      if (val < 100) val += v;
  };

  this.sub = function(v) {
      if (val > 0) val -= v;
  };

  this.getVal = function() {
      return val;
  };
};

View.js

MyApp.View = function(controller) {
  let $num = $('#num');
  let $incBtn = $('#increase');
  let $decBtn = $('#decrease');

  this.render = function(modal) {
      $num.text(modal.getVal());
  };

  /*  绑定事件  */
  $incBtn.click(controller.increase);
  $decBtn.click(controller.decrease);
};

说点啥

关于优势就是实现了代码分层,没啥可多说的,我主要提一点不足。认真观察Container可以发现内部还耦合了View层,这就影响到我们Container层的复用。

举个栗子,有A和B两个项目,A处于迭代期间,B处于初始期,B现在要做登录模块,除了UI,跟项目A的登录模块完全一致,如果A的登录模块的Container层不包含View层的内容的话,那我们可以直接在项目B中使用A模块的Container层,避免做重复的轮子。解决方案的话就是下面要说的MVP模式

MVP

快上车,更多详情在代码中解释
图片引用于阮一峰

  1. View与Modal不发生联系,都通过Presenter传递
  2. Presenter与另外两层的通信都是双向的
  3. View层需要遵循一定的协议(为了方便复用P层)

index.html

<html>
  <script src='./mvp/Presenter.js'></script>
  <script src='./mvp/BaseView.js'></script>
  <script src='./mvp/View.js'></script>
  <script src='./mvp/Modal.js'></script>

  
  <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
  <body>
    <h1 id='num'></h1>
    <button id='increase'>增加</button>
    <button id='decrease'>减小</button>
  </body>
</html>
<script>

// ##################### MVP #######################
  window.onload = function () {
    // MVP的入口一般是View层
    var view = new MyApp.View();
    view.init();
  }

</script>

BaseView.js

const BaseView = function () {
  this.render = function () {
    // 模拟接口
    console.error('子类必须实现的方法');
  }
}

View.js

class View extends BaseView {
  constructor() {
    super();
    this.$num = $('#num');
    this.$incBtn = $('#increase');
    this.$decBtn = $('#decrease');
  }

  /* 看View的定义处可以发现,该View是继承自BaseView的,作用如下:
   * 1. 在创建P时将View的引用,注意是View的引用传给了P,在P层要使用传进来的View更新UI,那么肯定要指定View层都有哪些接口啊,所以就通过BaseView来进行定义。
   * 2. 在复用P时,一般要带上BasaeView接口,这样可以快速知道该P都调用了View的哪些接口,也方便View来了解都需要实现什么接口
   * 
  */
  render = function(val) {
    this.$num.text(val);
  }

  init = function() {
    // 在View中创建创建Presenter并将View的引用传给Presenter对象,这样就实现了View和Presenter的解耦,方便该Presenter在其他项目中的同模块中复用
    var presenter = new MyApp.Presenter(this);

    this.$incBtn.click(presenter.increase);
    this.$decBtn.click(presenter.decrease);
  }
}

MyApp.View = View;

Presenter.js

const MyApp = {};

MyApp.Presenter = function(view) {
    let _modal = new MyApp.Modal();
    let _view = view;

    _view.render(_modal.getVal());


    this.increase = function() {
        _modal.add(1);
        _view.render(_modal.getVal());
    };

    this.decrease = function() {
        _modal.sub(1);
        _view.render(_modal.getVal());
    };
};

Modal.js

MyApp.Modal = function() {
  let val = 0;

  this.add = function(v) {
      if (val < 100) val += v;
  };

  this.sub = function(v) {
      if (val > 0) val -= v;
  };

  this.getVal = function() {
      return val;
  };
};

说点啥

  1. MVP弥补了MVC中C层不可复用的问题,其他也没啥
  2. 在了解MVC的基础上将上面MVP的示例自己敲写一遍理解会更加深刻。

MVVM

  1. 理论上分为三层ModalViewViewModal,主要是让ViewModal完全代替Controller,但现实是Controller不能完全被ViewModal代替
  2. Modal和View没有什么好说的,主要提一下ViewModal。其实ViewModal的本质是一个Modal,他的主要任务是把纯Modal数据转化成View用Modal。

index.html

<html>
  <script src='./MVVM/ViewModal.js'></script>
  <script src='./MVVM/View.js'></script>
  <script src='./MVVM/Modal.js'></script>

 
  <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
  <body>
    <input id="userName" placeholder="账号"/>
    <input id="pwd" placeholder="密码"/>
    <button id='login'>login</button>
    <h5 id="tip"></h5>
  </body>
</html>
<script>

// ############## MVVM-当前文件是尚未被完全取代的Container层 ###############
  window.onload = function () {
    let modal = new MyApp.Modal();
    
    // 创建ViewModal
    let viewModal = new MyApp.ViewModal(modal);

    $("#userName").on('input', function(e) {
      // View-Modal的数据绑定
      modal.userName = e.currentTarget.value;
      // 使用ViewModal中的getInputState来对输入值做验证
      let inputState = viewModal.getInputState(); 
      $('#tip').html(inputState);
    });
    $("#pwd").on('input', function(e) {
      // View-Modal的数据绑定
      modal.pwd = e.currentTarget.value;
      // 使用ViewModal中的getInputState来对输入值做验证
      let inputState = viewModal.getInputState();
      $('#tip').html(inputState);
    }); 

   
    $("#login").click(function() { 
      // Modal-View的数据绑定
      let inputState = viewModal.getInputState();
      const loginResult = viewModal.login();
      if (loginResult && inputState == INPUT_STATE.valid) {
        $('#tip').html('登录成功');
        return;
      }
      $('#tip').html('登录失败');
    });
  }
</script>

View.js

指的就是我们的html代码,不在单独写文件了

Modal.js

MyApp.Modal = function(userName, pwd) {
  this.userName = '';
  this.pwd = '';
};

ViewModal.js

const MyApp = {}; // 创建这个应用对象

const INPUT_STATE = {
    empty: 'empty',
    invalid: 'invalid',
    valid: 'valid',
}

MyApp.ViewModal = function(modal) {
  this.modal = modal;
  
  this.getInputState = function () {
    const userNameLength = this.modal.userName.length;
    const pwdLength = this.modal.pwd.length;
    if (userNameLength == 0 || pwdLength == 0) {
        return INPUT_STATE.empty;
    }
    if ((userNameLength < 5 || userNameLength > 10) || (pwdLength < 5 || pwdLength > 10)) {
        return INPUT_STATE.invalid;
    }
    return INPUT_STATE.valid;
  }

  this.login = function () {
      if (this.modal.userName == 'admin' && this.modal.pwd == 'admin') {
          return true;
      } 
      return false;
  }
};

说点啥

  1. 在写代码之前提到ViewModal就是将纯Modal转化成View要用的Modal,在上述示例中,转化如下:
  2. 将Modal中的userName和pwd转化成ViewModal中getInputState返回的inputState和login返回的loginResult, 使用得到的inputStateloginResult帮助我们实现UI的变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值