0
点赞
收藏
分享

微信扫一扫

MVC模式

楚木巽 2021-09-25 阅读 45

1. MVC模式

1.1 定义

M (model) 模型----V (view) 视图----C (controller)控制器. 一种将业务, 逻辑, 数据,视图分离的模式.

MVC模式是三个单词的缩写,这个模式将程序从结构上分成了三层

  1. Model层, 是核心的数据层,也就是程序需要操作的数据或者信息层
  2. view层,, 是直接面向用户的视图层.通常是提供给用户操作的见面.是程序的外壳
  3. Controller层, 它负责根据用户从视图层输入的指令, 来对数据层的Model进行相应的操作.产生最终的结果

1.2 为什么要使用MVC模式?

我们首先通过下面一个例子来了解一下为什么需要使用这种结构的模式

假设我们有如下的DOM结构

   <h3>我张三今天要吃苹果!</h3>
    <p class="buy">
        买:
        <input type="button" value="买个苹果">
        <input type="button" value="买个梨子">
    </p>
    <p class="eat">
        吃:
        <input type="button" value="吃个苹果">
        <input type="button" value="吃个梨子">
    </p>
    <p class="count">
        <b>张三</b>还剩:<span>0</span>个苹果,<span>0</span>个梨子
    </p>

我们想要实现点击买水果时,增加对应买的水果数量并显示在页面上, 在点击吃水果的按钮时减少剩余水果的数量并渲染到页面上.

在不使用MVC模式时,我们可以利用js写出如下非常简单的代码

  //不使用模式的代码
  let aButBuy = document.querySelectorAll('.buy input'),
      aButEat = document.querySelectorAll('.eat input'),
      aSpancount = document.querySelectorAll('.count span'),
      numArr = [0,0];// 分别对应苹果和梨子的个数
    aButBuy.forEach((item,index) =>{
      item.onclick = function(){
          numArr[index] === 20 ? alert('只能吃20个') :'';
          //限制苹果的个数最大不能大过20
          numArr[index] = Math.min(20,++numArr[index]);
          //操作DOM将数据渲染到视图上
          aSpancount[index].textContent = numArr[index]
      }
    });
    aButEat.forEach((item,index) =>{
        item.onclick = function () {
            numArr[index] === 0 ? alert('都没了还吃') :'';
            numArr[index] = Math.max(0,--numArr[index]);
            aSpancount[index].textContent = numArr[index]
        }
    })

我们不难发现操作对应视图的代码是几乎是相同的,所以我们可以将改变页面视图的代码封装成一个函数

//单独列出改变HTML即视图的操作, 这个模块相对独立, 只需要数据的支持
// target 表示对应的DOM元素, data为对应的渲染数据
  function updateView(target,data){
      target.textContent = data
  }

此时我们只需要在点击事件函数里面调用这个方法就行了.

aButBuy.forEach((item,index) =>{
    item.onclick = function(){
        numArr[index] === 20 ? alert('只能吃20个') :'';
        numArr[index] = Math.min(20,++numArr[index]);
        updateView(aSpanCount[index],numArr[index])
    }
});
aButEat.forEach((item,index) =>{
    item.onclick = function () {
        numArr[index] === 0 ? alert('都没了还吃') :'';
        numArr[index] = Math.max(0,--numArr[index]);
        updateView(aSpanCount[index],numArr[index])
    }
})

假设现在需要新则一个需求, 在页面显示一个总和用来表示当前水果的总个数,此时我们只需要改变updateView方法的就可以完成需求.

我们在对应的HTML结构中新增如下的

 <p class="total">
       <b>张三</b>总共还剩:<span>0</span>个水果。
 </p>
let totalSpan = document.querySelector('.total span');
 function updateView(target,data){
     target.textContent = data;
     totalSpan.textContent = numArr.reduce((total,cur) => total + cur).toString()
}

到这一步我们可以看到列出某个模块的好处我们可以进行将单个模块单独列出来,
我们可以继续到对应的数据操作封装成一个独立的函数.使得代码更加健壮.

// 单独列出计数器的变化, 即数据的变化, 这个模块需要和数据操作联系起来
 function dataModel(bool,index){
       if(bool){
           numArr[index] === 20 ? alert('只能吃20个') :'';
           numArr[index] = Math.min(20,++numArr[index]);
       }else{
           numArr[index] === 0 ? alert('都没了还吃') :'';
           numArr[index] = Math.max(0,--numArr[index]);
       }
    }

此时我们的最终代码如下

let aButBuy = document.querySelectorAll('.buy input'),
    aButEat = document.querySelectorAll('.eat input'),
    aSpanCount = document.querySelectorAll('.count span'),
    totalSpan = document.querySelector('.total span'),
    numArr = [0,0];// 分别对应苹果和梨子的个数
// 更新视图 target表示对应的DOM元素, data 表示对应渲染的数据
function updateView(target,data){
    target.textContent = data;
    totalSpan.textContent = numArr.reduce((total,cur) => total + cur).toString()
}
// 操作数据的函数 bool 为true表示累加, 否者累减
function dataModel(bool,index){
    if(bool){
        numArr[index] === 20 ? alert('只能吃20个') :'';
        numArr[index] = Math.min(20,++numArr[index]);
    }else{
        numArr[index] === 0 ? alert('都没了还吃') :'';
        numArr[index] = Math.max(0,--numArr[index]);
    }
}
aButBuy.forEach((item,index) =>{
    item.onclick = function(){
        dataModel(true,index);
        updateView(aSpanCount[index],numArr[index])
    }
});
aButEat.forEach((item,index) =>{
    item.onclick = function () {
        dataModel(false,index);
        updateView(aSpanCount[index],numArr[index])
    }
});

此时我们已经有了MVC的雏形.控制器C在前端主要放置在用户触发的一些事件的相关代码,就是上面的点击事件一样. 但是上面的代码,视图和数据模型其实还是全部耦合在控制器里面的,而且整体的代码结构不够模块化, 模块之间还是存在着不小的依赖,所以这仅仅是一个封装稍微好一点的代码,不够完美.

1.3 起步

接下来我们以MVC模式来实现这个需求, 首先我们先看一个关于MVC的简图

  1. 用户在V上与程序进行交互,触发控制器C,比如用户点击了一个按钮
  2. C触发向相应的事件, 根据具体需求让M做出对应的改变
  3. M变化之后会要求V根据实际做出对应的更新

此时关键点在于第三步, M与模型数据的变化, 会引起视图V的变化, 此时我们利用观察者模式或者代理模式来达到我们想要的的需求.

const MVC = (function () {
    //   Model
    let  Model= (function () {
        // 保存各个水果的数量
        let data = [1,2];// [苹果,梨子]
        return {
            // 数据更新接口
            updateData(bool,index){
                this.handleData(bool,index);
                //发布
                Model.Observer.emit(data);
            },
            // 处理数据接口
            handleData(index,bool){
                if(bool){
                    data[index] === 20 ? alert('只能吃20个') :'';
                    // 限定水果的数量不能超过20
                    data[index] = Math.min(20,++data[index]);
                }else{
                    data[index] === 0 ? alert('都没了还吃') :'';
                    // 限定水果的数量不能小于0
                    data[index] = Math.max(0,--data[index]);
                }
            },
            // 提供观察者接口
            Observer:(function () {
                let callbackArr = [];
                return {
                    // 订阅
                    on(callback){
                        callbackArr.push(callback);
                    },
                    // 发布
                    emit(data){
                        // 遍历执行对应的回调函数并传入数据
                        callbackArr.forEach(cb => cb(data));
                    }
                }
            })(),
            // 获取数据接口
            getData(){
                return data
            }
        }
    })();
    //Controller
    let View = (function () {
        // 获取对应的DOM;
        const  aCount = document.querySelectorAll('.count span'),
              oTotal = document.querySelector('.total span');
        return {
            // 更新视图
            updateView(data){
                aCount.forEach((item,index) =>{
                    aCount[index].textContent = data[index];
                });
                oTotal.textContent = data.reduce((total,cur) => total+cur);
            }
        }
    })();
    let Controller = function () {
        // 对应的DOM获取
        let aButBuy = document.querySelectorAll('.buy input'),
            aButEat = document.querySelectorAll('.eat input');
        aButBuy.forEach((item,index) =>{
            item.onclick = function () {
                // 触发数据更新
                Model.updateData(index,true)
            }
        });
        aButEat.forEach((item,index) =>{
            item.onclick = function () {
                // 触发数据更新
                Model.updateData(index,false)
            }
        });
    };
    return{
        init(){
            //初始化视图
            View.updateView(Model.getData());
            // 添加控制器
            Controller();
            // 订阅View的更新
            Model.Observer.on(View.updateView);
        }
    }
})();
MVC.init();

1.4 总结

MVC模式的优点:将每个模块逻辑分开, 降低了代码的耦合, 可以分模块进行开发,较低了开发和后代维护的成本.

MVC模式通常用于大型应用程序的开发, 因为分层较低了代码的耦合性,有助于管理复杂的应用程序.

MVC模式的缺点:

  1. 结构分离带来的结果就是代码更多更加复杂, 所以小型的需求使用MVC模式是没有必要的,这样写起来会麻烦很多
  2. 控制层和视图层并没有真正的分离,假设我们点击的案列也会变化的话, 就还需要在View层在获取按钮等等操作.但是这个问题可以被解决的, 也就是后面衍生的MV*, 都是在MVC的原理基础上扩展出来的.
举报

相关推荐

0 条评论