老生常谈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中eval函数的使用方法与示例
Apr 09 Javascript
jquery实现点击展开列表同时隐藏其他列表
Aug 10 Javascript
js如何改变文章的字体大小
Jan 08 Javascript
浅析C/C++,Java,PHP,JavaScript,Json数组、对象赋值时最后一个元素后面是否可以带逗号
Mar 22 Javascript
IONIC自定义subheader的最佳解决方案
Sep 22 Javascript
ros::spin() 和 ros::spinOnce()函数的区别及详解
Oct 01 Javascript
bootstrap组件之导航组件使用方法
Jan 19 Javascript
jQuery插件HighCharts绘制简单2D柱状图效果示例【附demo源码】
Mar 21 jQuery
用最简单的方法判断JavaScript中this的指向(推荐)
Sep 04 Javascript
vue移动UI框架滑动加载数据的方法
Mar 12 Javascript
详解如何快速配置webpack多入口脚手架
Dec 28 Javascript
JavaScript基于面向对象实现的无缝滚动轮播示例
Jan 17 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扩展ZF――Validate扩展
2008/01/10 PHP
php删除左端与右端空格的方法
2014/11/29 PHP
PHP异常处理定义与使用方法分析
2017/07/25 PHP
使用PHP开发留言板功能
2019/11/19 PHP
聊聊 PHP 8 新特性 Attributes
2020/08/19 PHP
jQuery select控制插件
2009/08/17 Javascript
JQuery实现倒计时按钮的实现代码
2012/03/23 Javascript
Javascript中的默认参数详解
2014/10/22 Javascript
jQuery中slideUp 和 slideDown 的点击事件
2015/02/26 Javascript
学习JavaScript设计模式(接口)
2015/11/26 Javascript
关于数据与后端进行交流匹配(点亮星星)
2016/08/03 Javascript
jQuery插件EasyUI设置datagrid的checkbox为禁用状态的方法
2016/08/05 Javascript
EsLint入门学习教程
2017/02/17 Javascript
nodejs更新package.json中的dependencies依赖到最新版本的方法
2018/10/10 NodeJs
Vue3项目打包后部署到服务器 请求不到后台接口解决方法
2020/02/06 Javascript
微信小程序实现拨打电话功能的示例代码
2020/06/28 Javascript
[01:25]DOTA2自定义游戏灵园鬼域等你踏足
2015/10/30 DOTA
[57:37]EG vs Mineski 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
python中实现定制类的特殊方法总结
2014/09/28 Python
Python中Django框架利用url来控制登录的方法
2015/07/25 Python
Python装饰器入门学习教程(九步学习)
2016/01/28 Python
python利用微信公众号实现报警功能
2018/06/10 Python
Django2.1集成xadmin管理后台所遇到的错误集锦(填坑)
2018/12/20 Python
django 自定义过滤器的实现
2019/02/26 Python
python分布式编程实现过程解析
2019/11/08 Python
解决Keras中CNN输入维度报错问题
2020/06/29 Python
python 如何使用find和find_all爬虫、找文本的实现
2020/10/16 Python
Pycharm 如何一键加引号的方法步骤
2021/02/05 Python
使用html5新特性轻松监听任何App自带返回键的示例
2018/03/13 HTML / CSS
继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么?
2015/11/18 面试题
UNIX文件系统分类
2014/11/11 面试题
医学专业五年以上个人求职信
2013/12/03 职场文书
项目合作协议书范本
2014/04/16 职场文书
2015年民主评议党员工作总结
2015/05/19 职场文书
工作服管理制度范本
2015/08/06 职场文书
MySQL 如何限制一张表的记录数
2021/09/14 MySQL