MVP模式

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-12-15

MVP(Model, View, Presenter)即模型、视图、管理器:View层不直接引用Model层内的数据,而是通过Presenter层实现对Model层内的数据访问。即所有层次的交互都发生在Presenter层。

MVC模式中,视图层常常因渲染页面直接引用数据层内的数据,对于发生的这一切,控制层常常不得而知。因此数据层内的数据修改,常常在控制器不知情的情况下影响到视图层的呈现。

这就是因为MVC模式中视图的创建与数据耦合在一起的缘故,而解决方法就是将视图层与数据层解耦,统一交由管理层管理,管理层来负责管理数据、UI视图创建、交互逻辑等等一切事务。因此管理层也就强大起来,这样数据层只负责存储数据,视图层只负责创建视图模板,因此MVP模式其实是MVC模式演变出来的。

F.module('lib/MVP', function() {
  /** 通过MVP构造函数来新增管理器模块 */
  var MVP = function (modName, pst, data) {
    MVP.model.setData(modName, data);
    MVP.presenter.add(modName, pst)
  }

  var formatString = function (str, data) {
    return str.replace(/{#(\w+)#}/g, function(match, key) {
      return typeof data[key] === undefined ? '' : data[key]
    })
  }

  // 数据层模型 
  // 自执行函数 返回一个数据模型层对象操作方法
  MVP.model = (function() {
    var M = {};
    M.data = {
      // 左侧导航栏展示相关的数据
      slideBar: [
        {
          text: '动漫',
          title: '动漫title',
          content: '动漫content',
          href: 'https://www.baidu.com'
        },
        {
          text: '电竞',
          title: '电竞title',
          content: '电竞content',
          href: 'https://www.baidu.com'
        },
        {
          text: '新闻',
          title: '新闻title',
          content: '新闻content',
          href: 'https://www.baidu.com'
        },
      ],
    };

    return {
      getData: function(key) {
        return M.data[key];
      },
      setData: function(key, value) {
        M.data[key] = value;
        return this;
      },
    }
  }());

  // 视图层模型
  // 自执行函数 返回一个根据视图名称返回视图的函数
  MVP.view = (function() {
    // 视图对象
    var views = {
      createSlideBar: function(data) {
        var html = '';
        if (!data || !data.length) {
          return;
        }

        var dom = document.createElement('div');
        dom.classList.add('slidebar');
        dom.id = 'slidebar';

        var tpl = {
          container: [
            '<div class="slidebar-inner"><ul>{#content#}</ul></div>',
          ].join(''),
          item: [
            '<li style="display: flex; height: 100px;">',
              '<a class="icon" href="{#href#}" style="display: inline-block">',
                '<span>{#text#}</span>',
              '</a>',
              '<div class="box" style="display: none;">',
                '<p>',
                  '{#title#}',
                '</p>',
                '<p>',
                  '{#content#}',
                '</p>',
              '</div>',
            '</li>'
          ].join('')
        };

        for (var i = 0, len = data.length; i < len; i++) {
          html += formatString(tpl.item, data[i]);
        }
        return formatString(tpl.container, { content: html });
      }
    };

    return function(name, data) {
      return views[name](data);
    }
  }());

  // 控制器层
  MVP.presenter = (function() {
    var view = MVP.view;
    var model = MVP.model;
    var C = {
      slideBar: function(model, view) {
        var data = model.getData('slideBar');
        var slideBarView = view('createSlideBar', data);
        var dom = document.createElement('div');
        dom.classList.add('slidebar');
        dom.id = 'slidebar';
        dom.innerHTML = slideBarView;
        document.body.appendChild(dom);
        var ul = document.getElementsByTagName('ul')[0];
        ul.addEventListener('mouseover', function(e) {
          if (e.target.nodeName === 'LI') {
            var box = e.target.getElementsByClassName('box')[0];
            box.style.display = 'block';
          }
        });

        ul.addEventListener('mouseout', function(e) {
          if (e.target.nodeName === 'LI') {
            var box = e.target.getElementsByClassName('box')[0];
            box.style.display = 'none';
          }
        });
      }
    };

    return {
      init: function() {
        for (var key in C) {
          C[key] & C[key](model, view, key)
        }
      },
      add: function(modName, pst) {
        C[modName] = pst;
        return this;
      }
    }
  }());

  MVP.init = function() {
    this.presenter.init();
  };

  window.MVP = MVP;

  return MVP
});

在业务中这样使用:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <!-- 参照异步模块模式中的F对象 -->
  <script src="./f.js"></script>
  <script>
    /** 增加一个模块 */
    F.module(['lib/MVP'], function (MVP) {
      MVP('sites', function (M, V, modName) {
        var data = M.getData('sites'); // 从数据层获取数据,可以调用多个
        // 因为是新增的模板 所以模板和交互要自己定义
        // 这些交互也是存在presenter的C对象中 定义之后别的地方也可以使用
        if (!data || !data.length) {
          return;
        }

        var tpl = {
          container: [
            '<div>',
              '{#content#}',
            '</div>'
          ].join(''),
          item: [
            '<p>',
              '<a href="{#href#}" style="color:pink">{#text#}</a>',
            '</p>'
          ].join(''),
        }

        var html = '';

        /** 在模块化管理下,这个也可以抽成一个模块 不用每个视图渲染代码中都定义 */
        var formatString = function (str, data) {
        return str.replace(/{#(\w+)#}/g, function(match, key) {
          return typeof data[key] === undefined ? '' : data[key]
        })
      }

        for (var i = 0, len = data.length; i < len; i++) {
          html += formatString(tpl.item, { text: data[i].name, href: data[i].site } );
        }

        html = formatString(tpl.container, { content: html });

        var dom = document.createElement('div');
        dom.classList.add('site-wraps');
        dom.innerHTML = html;
        document.body.appendChild(dom);
      }, [
        {
          name: '淘宝',
          site: 'https://www.taobao.com',
        },
        {
          name: '京东',
          site: 'https://www.jd.com',
        },
        {
          name: '百度',
          site: 'https://www.baidu.com',
        }
      ])
    })

    window.onload = function () {
      MVP.init();
    }
  </script>
</body>
</html>

MVP与MVC相比最重要的特征就是MVP中将视图层与数据层完全解耦,使得对视图层的修改不会影响到数据层,数据层内的数据改动不会影响视图层,因此,我们在管理器中对数据或者视图灵活地调用就可使数据层内的数据与视图层内的视图得到更高效的复用。因此,MVP模式也可以实现一个管理器,可以调用多个数据,或者创建多种视图,而且是不受限制的,因而管理器有更高的操作权限,因此对于业务逻辑与需求的实现只需专注于管理器开发即可,当然管理器内过多的逻辑也使得开发与维护的成本提高。