1. MVC思想以及实现

MVC模型基础理论

MVC英文MVC``Model-View-Controller是软件设计中的一种模式,通常用于实现用户界面、数据和控制逻辑。它强调了软件的业务分离逻辑和显示之间的分离。这种“关注点分离”提供了更好的劳动分工和更好的维护。其它一些设计模式是基于MVC的,比如MVVMMVW
MVC软件设计模式的三个部分可以描述如下:

  1. Model:管理数据和业务逻辑。
  2. View:处理布局与视图模板。
  3. Controller:处理数据和视图关联挂载和基本的逻辑操作。

前端中的MVC

前端中的MVC分为:

  1. Model:管理视图所需要的数据、数据的关联。
  2. ViewHTML模板与视图渲染。
  3. Controller:管理事件逻辑。

MVC模式实现计算器

我们现在通过MVC模式来实现一个计算器的功能。既然MVC层中牵扯到ModelViewController层,那么我们就要搞清楚这三个层中到底控制了些什么?
首先是Model层:Model层管理视图所需要的数据,对于计算器来说需要用到哪些数据data呢?首先肯定包含两个需要计算的数字,我们先称为number1、number2。肯定还存在计算的方式,既然是计算那么肯定有加减乘除的方式,我们先称为type。最后肯定还要保存计算的结果res
其次是View层:View层管理视图和视图渲染,我们要在视图层中构建HTML模板。
最后是Controller层:控制器contoller层包含绑定的事件处理函数,因为当我们点击计算的按钮时,视图要能够随时计算出更新后的结果。
计算器整个流程中其实包含双向绑定的过程:

  1. 首先是controller触发事件更新Model层数据,Model层数据更新后通过View层更新渲染。
  2. 其次是View层手动input更新数据,View层更新通知controller层控制模型层Model中的数据更新。

MVC模式实现计算器的步骤

  1. 搭建MVC模式需要的架子,首先是Model、View、Controller模型层。我需要把所有的视图都放到根元素#app容器内部,所以要在视图中手动创建#app根元素。
// 视图
<div id="app"></div>

// 基本MVC逻辑的架子
;(function(){
  function init(){
    model.init(); // 组织数据 + 数据监听操作 / 数据代理
    view.render();// 组织HTML模版+渲染HTML模版
    controller.init(); // 事件处理函数定义与绑定
  };
  init();
})()

  1. 开始编写Model层的逻辑,Model模型层处理的是什么问题呢?Model其实处理就是数据劫持、数据响应的问题。
    1. 首先我们要在Model模型层保存计算器功能需要用到的数据a、b、s、r
    2. 其次我们要用数据劫持的逻辑处理下一步问题。为什么需要用到数据劫持呢?因为我们现在想访问到Model中的data数据的话,我们需要通过Model.data.a的方式进行访问。而我需要的是直接通过Model.a的方式进行访问。其次是因为当Model模型层中的数据发生改变的时候,View层视图也会随着更新。换句话说,就是当我们操作了Model模型层中的数据时,我们不仅仅需要更新Model层中的数据,还需要操作视图的更新。所以要用到数据劫持的方式。
    3. 在枚举_this.data对象的时候,为什么我们要用立即执行函数呢?如果不添加立即执行函数的话,当我们在触发getter、setter函数的时候,getter、setter函数内部访问到的key都是循环之后的结果。通过立即执行函数,让getter、setter函数能够在自己的词法作用域内部存在私有变量key。换句话说,就是getter、setter函数是闭包函数,通过立即执行函数创建一个独立的作用域,闭包函数能够访问到独立作用域中的私有变量key
  var model = {
    data:{
      a:0, // input1
      b:0, // input2
      s:'+', // 方法
      r:0 // 结果
    },
    init:function(){
      // 保存this指向
      var _this = this;
      // 枚举_this.data属性进行数据劫持
      for (const k in _this.data) {
        if (Object.hasOwnProperty.call(_this.data, k)) {
          (function(k){
            Object.defineProperty(_this,k,{
              get:function(){
                // model.a -> get
                return _this.data[k];
              },
              set:function(newValue){
                // model.a = 123   - set
                // 更新Model模型层的数据
                _this.data[k] = newValue;
                // view.update({[k]:newValue});
              }
            })
          })(k)
        }
      }
    }
  };
  1. 开始编写View层中的逻辑,View模型层中需要处理的逻辑是什么呢?首先肯定是视图模板、视图渲染、视图更新的逻辑。
    1. 首先编写模板视图
    2. View视图层目前的逻辑分为两部分,首先是视图初始化渲染,什么是视图初始化渲染呢?也就是视图第一次渲染,将所有的视图都进行渲染显示。其次就是视图更新渲染,当Model数据发生变化时,我们要部分渲染视图。所以View视图层暂时就分为render、update两种方法。
    3. render函数渲染的逻辑思路:首先我们需要将View模板中的{{ a }}内容进行替换,我们要把花括号内部的内容全部替换成与Model模型层数据对应的值。所以我们通过正则表达式,将{{}}内容取出,然后用Model模型层中的数据进行替换。替换之后,我们要将替换后的模板内容当作#app容器的内容。
    4. update函数渲染的逻辑思路:首先update函数会接受一个需要更新的对象,对象内部的键值对表示更新的Model数据和更新后的值。所以我们可以枚举这个更新的对象,然后通过document.querySelector查找出需要更新的DOM元素,将该DOM元素修改为更新后的值即可。为什么要通过类名的方式寻找DOM元素呢?因为我们没有进行收集依赖的操作,我们只有通过类名的方式找到需要更新的DOM元素。
    5. 到目前为止,我们实现了MVC模式中Model层与View层的关系,Model层中的数据发生变化时,View层就会更新视图。
  var view = {
    el:'#app',
    template:`
      <p>
        <span class="cal-a">{{a}}</span>
        <span class="cal-s">{{s}}</span>
        <span class="cal-b">{{b}}</span>
        <span>=</span>
        <span class="cal-r">{{r}}</span>
      </p>
      <p>
        <input type="text" placeholder="Number a" class="cal-input a" />
        <input type="text" placeholder="Number b" class="cal-input b" />
      </p>
      <p>
        <button class="cal-btn">+</button>
        <button class="cal-btn">-</button>
        <button class="cal-btn">*</button>
        <button class="cal-btn">/</button>
      </p>
    `,
    render:function(){
        this.template = this.template.replace(/\{\{(.*?)\}\}/g,function(node,key){
          return model[key]
        })
        var container = document.createElement('div');
        container.innerHTML = this.template;
        document.querySelector(this.el).appendChild(container);
    },
    update:function(mutedData){
      if(!mutedData) return;
      for (const k in mutedData) {
        if (Object.hasOwnProperty.call(mutedData, k)) {
          document.querySelector('.cal-'+k).textContent = mutedData[k]
        }
      }
    }
  };
  1. 开始编写Controller层的逻辑。Controller模型层的逻辑是什么呢?Controller模型层肯定是事件处理函数的绑定、事件处理的逻辑。
    1. 首先我们需要处理input输入框的逻辑,当输入框输入值的时候,视图要随之发生改变。编写input输入框事件处理函数的逻辑也比较简单,首先我们需要通过querySelectorAll操作所有的input元素节点,然后依次绑定给其绑定事件处理函数。然后我们需要获取当前事件源对象输入的value值,然后将value赋值给Model模型层中对应的数据。最后再重新计算当前Modelres结果。

ezgif.com-gif-maker (1).gif

  1. 其次我们处理计算按钮点击的逻辑,当点击按钮时,需要更新计算的结果,并且视图也要随着发生改变。按钮的事件处理函数绑定,我用的是事件委托的方式进行处理。其实逻辑与input元素绑定事件处理函数相同,都是给Model模型层对应的数据赋值之后,再重新计算Model模型层中res的值。ezgif.com-gif-maker.gif
  2. 注意在计算Model数据层中的值时,用到了with语句和eval()函数,with语句主要目的是修改当前作用域链的顶层作用域链。而eval语句是为了能够通过字符串的形式计算Model数据层中的res数据结果。
  var controller = {
    init:function(){
      var oCalInputs = document.querySelectorAll('.cal-input'),
          oCalBtns = document.querySelectorAll('.cal-btn'),
          btnItem,
          inputItem;
      for (let i = 0; i < oCalInputs.length; i++) {
        inputItem = oCalInputs[i];
        inputItem.addEventListener('input',this.handleInput,false);        
      };
      for (let i = 0; i < oCalBtns.length; i++) {
        btnItem = oCalBtns[i];
        btnItem.addEventListener('click',this.handleBtnClick,false);      
      }
    },
    handleInput:function(e){
      var tar = e.target,
          value = Number(tar.value),
          field = tar.className.split(' ')[1];
      model[field] = value;
      // model.r = eval('model.a'+model.s+'model.b')
      with(model){
        r = eval('a'+s+'b')
      }
    },
    handleBtnClick:function(e){
      var type = e.target.textContent;
      model.s = type;
      with(model){
        r = eval('a'+s+'b')
      }
    }
  }

到目前为止,我们MVC模式的计算器功能已经完全实现。仔细想一想,MVC模型中的每个模型层分别注重的是什么?
Model模型层:保存计算器中用到的数据、主要处理数据劫持、数据保存功能。
View模型层:处理计算器的视图渲染、主要处理模板渲染、模板更新功能。
Controller模型层:处理输入框事件处理函数绑定、按钮事件处理函数绑定、主要处理事件、处理事件处理函数的绑定。
我们发现MVC模型并不是为了解决DOM操作的问题,而是将每个模型层进行分离。每个模型层关注点不同。而我们之前说过MVC模型中的双向绑定,在计算器开发的过程中也体现出来:
计算器整个流程中其实包含双向绑定的过程:

  1. 首先是controller触发事件更新Model层数据,Model层数据更新后通过View层更新渲染
  2. 其次是View层手动input更新数据,View层更新通知controller层控制模型层Model中的数据更新。
  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

️不倒翁

你的鼓励就是我前进的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值