老生常谈js中的MVC


Posted in Javascript onJuly 25, 2017

MVC是什么?

MVC是一种架构模式,它将应用抽象为3个部分:模型(数据)、视图、控制器(分发器)。

本文将用一个经典的例子todoList来展开(代码在最后)。

老生常谈js中的MVC

一个事件发生的过程(通信单向流动):

1、用户在视图 V 上与应用程序交互

2、控制器 C 触发相应的事件,要求模型 M 改变状态(读写数据)

3、模型 M 将数据发送到视图 V ,更新数据,展现给用户

js传统开发模式,大多基于事件驱动的

1、hash驱动

2、DOM事件,用来驱动视图

3模型事件(业务模型事件和数据模型事件),用来驱动模型和模型结合

所以js中的mvc的特点是:单向流动、事件驱动

一)模型

模型存放应用的所有数据对象业务数据、数据校验、增删改查),比如,例子todoList中的store模型,存放每一条记录与之有关的逻辑。

数据是面向对象的,当控制器请求模型读写数据时,模型就将数据包装成模型实例。任何定义在这个数据模型上的函数或逻辑都可以直接被调用。在本文的例子中采用localSrorage也是类似道理的。存储的Todos可以随时被调用

模型不关心,不包含视图和控制器的逻辑。它们应该是互相解耦的。这里提一点,模型视图的耦合显然是违反MVC架构原则,但往往我们有时候却因为业务关系而无法完全解耦

模型表现了领域特定的数据当一个模型有所改变的时候它会通知它的观察者(视图)

二)视图

视图是呈现给用户的,是用户交互的第一入口。它定义配置管理着每个页面相应的模板与组件,它表现一个模型的当前状态视图通过观察者模式监视模型,以获得最新的数据,来呈现最新的页面所以,页面首次加载时,往往是从接收模型的数据开始。

三)控制器

控制器分发器),是模型和视图之间的桥梁集中式配置和管理事件分发、模型分发、视图分发,还用来权限控制、异常处理等。我们的应用中往往是有多个控制器的

页面加载完成后,控制器监听视图的用户交互按钮点击或表单提交一旦用户发生交互时控制器做出对视图的选择触发控制器的事件处理机制去派发新的事件,通知模型更新数据(这样就回到了第一步了)

Demo-todoList

最后这里是一个用原生js写的todoLIst,这个demo做的很简陋,点击输入文字点击确定就添加,删除是直接点击该行信息。

单独分离开来举例子不好讲,所以在代码中进行注释。首先简单理下下边代码的思路:

1、V层定义配置了一个显示数据的字符串模板,同时定义一个订阅者的回调函数render() 用于页面更新数据。

2、C层监听用户的添加与删除操作,添加是add() 函数 它执行了回调函数render,同时向M层写入数据,通知M层改变。删除操作同理。

3、M层是本地存储localStorage,模拟一个存储数据对象的后台模型。

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>todo</title>
</head>
<body>
<header>
 <h3>待定事项</h3>
</header>
<main>
 <ul id="todoList"></ul>
 <input type="text" id="content">
 <button id="confirm">确认</button>
</main>

<script>
 (function () {
 const ADD_KEY = '__todoList__'

 const Utils = {
  // 模拟 Modal(实体模型)
  store(key, data) {
  if (arguments.length > 1) {
   return localStorage.setItem(key, JSON.stringify(data));
  } else {
   let storeData = localStorage.getItem(key);
   return (storeData && JSON.parse(storeData)) || []; // 这里一定要设置初始值为 []
  }
  }
 }

 class Todo {
  constructor(id, text = "") {
  this.id = id
  this.text = text
  }
 }

 let App = {
  init() {
  // this.todos 为一个存储json对象的数组, 是一个实例化的数据对象,可任意调用
  this.todos = Utils.store(ADD_KEY)
  this.findDom()
  this.bindEvent()
  this.render() // 初始化渲染
  },


  findDom() {
  this.contentBox = document.querySelector("#content")
  this.confirm = document.querySelector("#confirm")
  this.todoList = document.querySelector("#todoList")
  this.todoListItem = document.getElementsByTagName("li")
  },

  // 模拟 Controller (业务逻辑层)
  bindEvent() {
  this.confirm.addEventListener('click', () => {
   // 要求模型 M 改变状态,add()函数是写入数据操作
   this.add()
  }, false)

  this.todoList.addEventListener('click', (item) => { // 事件委托,优化性能
   this.remove(item)
  }, false)
  },

  // 这里勉强抽象成一个视图吧!!!
  view() {
  let fragment = document.createDocumentFragment() // 减少回流次数
  fragment = ''

  for (let i = 0; i < this.todos.length; i++) { // 一次性DOM节点生成
   // 这里使用拼接字符串代替视图的模板,
   // *******注意模板并不是一个视图,模板是由视图定义配置出来的,并被其管理着*******
   // 模板是用一种声明的方式指定部分甚至所有的视图对象
   fragment += `<li>${this.todos[i].text}</li>`
  }
  this.todoList.innerHTML = fragment
  },

  // render()函数作为一个订阅者的回调函数,数据的变化会反馈到模型 store
  // 换句话说:视图通过观察者模式,观察模型 store,当模型发生改变,触发视图更新
  render() {
  this.view()

  /**
   * 这里需要特别提一下,按照 MVC 原则这里本不应该出现下面的代码的
   * 因为业务逻辑关系(我本地存储使用的是同一个key值,再次写入数据会覆盖原来的数据,),
   * 所以必须通知模型 M 保存数据, V 层处理了不该它处理的逻辑,导致 M 与 V 耦合
   *
   * 解决办法是:将其抽象出来编写一个 视图助手 helper
   */
  Utils.store(ADD_KEY, this.todos)
  },

  getItemIndex(item) {
  let itemIndex
  if (item.target.tagName.toLowerCase() === 'li') {
   let arr = Array.prototype.slice.call(this.todoListItem)
   let index = arr.indexOf(item.target)
   return itemIndex = index
  }
  },

  add(e) {
  let id = Number(new Date())
  let text = this.contentBox.value
  let addTodo = new Todo(id, text)
  this.todos.unshift(addTodo) // 模型发生改变
  this.render() // 当模型发生改变,触发视图更新
  },

  remove(item) {
  let index = this.getItemIndex(item)
  this.todos.splice(index, 1)
  this.render()
  }
 }

 App.init()
 })()
</script>
</body>
</html>

随着界面和逻辑的复杂,用js或者jq去控制DOM不现实的。上边例子只是用原生js模拟mvc的思想实现过程。真正地项目往往会依赖一些封装好的优秀库进行高效开发。

mvc模式的优点

mvc编程把所有精力放在数据处理,尽可能减少对网页元素的处理。对于一定数量功能的网页,Mvc模式下强制规范代码简化减少重复代码,使代码易于扩充

mvc模式的弊端

1、清晰的构架以代码的复杂性为代价, 对小项目反而降低开发效率。 (如果本文的例子todoList用面条式代码编写,那得多简单啊!!!)
2、控制层和视图层耦合,导致没有真正分离和重用

3、在同一业务逻辑下,如果存在多种视图呈现,需要视图定义配置多个模板引擎、数据解析,多次处理数据与页面更新。代码就充满了各种选择器与事件回调,随着业务的膨胀,变得难以维护。

总结:其实,现在MVC在前端用得比较少了,因为它的局限性,催生了MVVM模式的流行与广泛使用,在下篇文章我会谈谈我对MVVM的理解,以及为何我使用基于MVVM模式的vue框架来高效开发。

以上这篇老生常谈js中的MVC就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript动态判断html元素并执行不同的操作
Jun 16 Javascript
jQuery基础知识小结
Dec 22 Javascript
JavaScript中innerHTML,innerText,outerHTML的用法及区别
Sep 01 Javascript
js动态生成Html元素实现Post操作(createElement)
Sep 14 Javascript
AngularJS基础 ng-show 指令简单示例
Aug 03 Javascript
修改ligerui 默认确认按钮的方法
Dec 27 Javascript
BootStrap模态框和select2合用时input无法获取焦点的解决方法
Sep 01 Javascript
基于$.ajax()方法从服务器获取json数据的几种方式总结
Jan 31 Javascript
玩转vue的slot内容分发
Sep 22 Javascript
JavaScript 继承 封装 多态实现及原理详解
Jul 29 Javascript
vue项目部署到nginx/tomcat服务器的实现
Aug 26 Javascript
Vue2.0 实现页面缓存和不缓存的方式
Nov 12 Javascript
教你5分钟学会用requirejs(必看篇)
Jul 25 #Javascript
浅谈Vue.js 1.x 和 2.x 实例的生命周期
Jul 25 #Javascript
Vue项目中引入外部文件的方法(css、js、less)
Jul 24 #Javascript
基于JavaScript实现百度搜索框效果
Jun 28 #Javascript
深入理解基于vue-cli的vuex配置
Jul 24 #Javascript
JS按条件 serialize() 对应标签的使用方法
Jul 24 #Javascript
vue2.0的contextmenu右键弹出菜单的实例代码
Jul 24 #Javascript
You might like
discuz的php防止sql注入函数
2011/01/17 PHP
PHP flush()与ob_flush()的区别详解
2013/06/03 PHP
smarty模板中拼接字符串的方法
2014/02/14 PHP
ThinkPHP有变量的where条件分页实例
2014/11/03 PHP
ext form 表单提交数据的方法小结
2008/08/08 Javascript
常见效果实现之返回顶部(结合淡入、淡出、减速滚动)
2012/01/04 Javascript
js实现二代身份证号码验证详解
2014/11/20 Javascript
javascript实现回车键提交表单方法总结
2015/01/10 Javascript
AngularJS实现表单验证
2015/01/28 Javascript
JavaScript实现更改网页背景与字体颜色的方法
2015/02/02 Javascript
javascript算法题:求任意一个1-9位不重复的N位数在该组合中的大小排列序号
2015/04/01 Javascript
JS调用Android、Ios原生控件
2017/01/06 Javascript
JavaScript中清空数组的三种方式
2017/03/22 Javascript
详解vue前后台数据交互vue-resource文档
2017/07/19 Javascript
vue.js分页中单击页码更换页面内容的方法(配合spring springmvc)
2018/02/10 Javascript
vue自定义指令的创建和使用方法实例分析
2018/12/04 Javascript
微信小程序公用参数与公用方法用法示例
2019/01/09 Javascript
详解原生JS动态添加和删除类
2019/03/26 Javascript
js神秘的电报密码 哈弗曼编码实现
2019/09/10 Javascript
[45:38]DOTA2上海特级锦标赛主赛事日 - 1 胜者组第一轮#1Liquid VS Alliance第一局
2016/03/02 DOTA
Python制作数据导入导出工具
2015/07/31 Python
Python爬虫番外篇之Cookie和Session详解
2017/12/27 Python
Python计算开方、立方、圆周率,精确到小数点后任意位的方法
2018/07/17 Python
pyqt5 实现多窗口跳转的方法
2019/06/19 Python
Python字符串对象实现原理详解
2019/07/01 Python
python提取照片坐标信息的实例代码
2019/08/14 Python
Numpy的简单用法小结
2019/08/28 Python
2020最新pycharm汉化安装(python工程狮亲测有效)
2020/04/26 Python
HTML5中的Web Notification桌面通知功能的实现方法
2019/07/29 HTML / CSS
德国购买门票网站:ADticket.de
2019/10/31 全球购物
高三毕业生自我鉴定
2013/12/20 职场文书
《花的勇气》教后反思
2014/02/12 职场文书
擅自离岗检讨书
2014/09/12 职场文书
派出所正风肃纪剖析材料
2014/10/10 职场文书
HTML5中 rem适配方案与 viewport 适配问题详解
2021/04/27 HTML / CSS
Html5同时支持多端sdk的小技巧
2021/11/17 HTML / CSS