老生常谈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 相关文章推荐
两种WEB下的模态对话框 (asp.net或js的分别实现)
Dec 02 Javascript
使用UglifyJS合并/压缩JavaScript的方法
Mar 07 Javascript
Ajax执行顺序流程及回调问题分析
Dec 10 Javascript
JavaScript取得键盘按下方向键是哪个的方法
Aug 04 Javascript
简单实现IONIC购物车功能
Jan 10 Javascript
jquery中封装函数传递当前元素的方法示例
May 05 jQuery
基于JS对象创建常用方式及原理分析
Jun 28 Javascript
linux 后台运行node服务指令方法
May 23 Javascript
详解VUE单页应用骨架屏方案
Jan 17 Javascript
vue踩坑记-在项目中安装依赖模块npm install报错
Apr 02 Javascript
有关vue 开发钉钉 H5 微应用 dd.ready() 不执行问题及快速解决方案
May 09 Javascript
微信小程序实现底部弹出框
Nov 18 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
PHP的栏目导航程序
2006/10/09 PHP
php通过function_exists检测函数是否存在的方法
2015/03/18 PHP
php给一组指定关键词添加span标签的方法
2015/03/31 PHP
thinkphp5实现微信扫码支付
2019/12/23 PHP
js版本A*寻路算法
2006/12/22 Javascript
jquery之Document元素选择器篇
2008/08/14 Javascript
常用简易JavaScript函数
2009/04/09 Javascript
jQuery 判断元素上是否绑定了事件
2009/10/28 Javascript
Jquery选中或取消radio示例
2013/09/29 Javascript
iframe子页面获取父页面元素的方法
2013/11/05 Javascript
jquery实现相册一下滑动两次的方法
2015/02/09 Javascript
jQuery多个input求和的实现方法
2015/02/12 Javascript
javascript实现图像循环明暗变化的方法
2015/02/25 Javascript
Angularjs 滚动加载更多数据
2016/03/17 Javascript
基于React.js实现原生js拖拽效果引发的思考
2016/03/30 Javascript
详解AngularJS 模态对话框
2016/04/07 Javascript
D3.js实现文本的换行详解
2016/10/14 Javascript
js实现点击图片自动提交action的简单方法
2016/10/16 Javascript
JS+WCF实现进度条实时监测数据加载量的方法详解
2017/12/19 Javascript
vue实现简单学生信息管理
2020/05/30 Javascript
python操作CouchDB的方法
2014/10/08 Python
python使用pipeline批量读写redis的方法
2019/02/18 Python
Python 3.6 -win64环境安装PIL模块的教程
2019/06/20 Python
python读写csv文件方法详细总结
2019/07/05 Python
Django ModelForm组件使用方法详解
2019/07/23 Python
大专学生推荐信范文
2013/11/19 职场文书
经典促销广告词大全
2014/03/19 职场文书
2014年幼儿园老师工作总结
2014/12/05 职场文书
和谐家庭事迹材料
2014/12/20 职场文书
努力工作保证书
2015/02/28 职场文书
2015年食品安全宣传周活动总结
2015/07/09 职场文书
2016年暑期见闻作文
2015/11/25 职场文书
2016思想纪律作风整顿心得体会
2016/01/23 职场文书
初中思品教学反思
2016/02/20 职场文书
2019七夕节祝福语36句,快来收藏吧
2019/08/06 职场文书
Windows Server 2019 安装DHCP服务及相关配置
2022/04/28 Servers