简单谈谈js中的MVC

MVC是什么?html

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

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

 

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

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

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

三、模型 M 将数据发送到视图 V ,更新数据,展示给用户架构

 

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

一、hash驱动框架

二、DOM事件,用来驱动视图函数

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

因此js中的mvc的特色是:单向流动、事件驱动

 

一)模型

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

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

模型不关心,不包含视图和控制器的逻辑。它们应该是互相解耦的。这里提一点,模型视图的耦合显然是违反MVC架构原则,但每每咱们有时候却由于业务关系而没法彻底解耦

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

 

二)视图

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

 

三)控制器

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

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

 

Demo-todoList

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

 

单独分离开来举例子很差讲,因此在代码中进行注释。首先简单理下下边代码的思路:

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

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

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

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4     <meta charset="UTF-8">
  5     <title>todo</title>
  6 </head>
  7 <body>
  8 <header>
  9     <h3>待定事项</h3>
 10 </header>
 11 <main>
 12     <ul id="todoList"></ul>
 13     <input type="text" id="content">
 14     <button id="confirm">确认</button>
 15 </main>
 16 
 17 <script>
 18   (function () {
 19     const ADD_KEY = '__todoList__'
 20 
 21     const Utils = {
 22       // 模拟 Modal(实体模型)
 23       store(key, data) {
 24         if (arguments.length > 1) {
 25           return localStorage.setItem(key, JSON.stringify(data));
 26         } else {
 27           let storeData = localStorage.getItem(key);
 28           return (storeData && JSON.parse(storeData)) || []; // 这里必定要设置初始值为 []
 29         }
 30       }
 31     }
 32 
 33     class Todo {
 34       constructor(id, text = "") {
 35         this.id = id
 36         this.text = text
 37       }
 38     }
 39 
 40     let App = {
 41       init() {
 42         // this.todos 为一个存储json对象的数组, 是一个实例化的数据对象,可任意调用
 43         this.todos = Utils.store(ADD_KEY)
 44         this.findDom()
 45         this.bindEvent()
 46         this.render() // 初始化渲染
 47       },
 48 
 49 
 50       findDom() {
 51         this.contentBox = document.querySelector("#content")
 52         this.confirm = document.querySelector("#confirm")
 53         this.todoList = document.querySelector("#todoList")
 54         this.todoListItem = document.getElementsByTagName("li")
 55       },
 56 
 57       // 模拟 Controller (业务逻辑层)
 58       bindEvent() {
 59         this.confirm.addEventListener('click', () => {
 60           // 要求模型 M 改变状态,add()函数是写入数据操做
 61           this.add()
 62         }, false)
 63 
 64         this.todoList.addEventListener('click', (item) => {  // 事件委托,优化性能
 65           this.remove(item)
 66         }, false)
 67       },
 68 
 69       // 这里勉强抽象成一个视图吧!!!
 70       view() {
 71         let fragment = document.createDocumentFragment()   // 减小回流次数
 72         fragment = ''
 73 
 74         for (let i = 0; i < this.todos.length; i++) { // 一次性DOM节点生成
 75           // 这里使用拼接字符串代替视图的模板,
 76           // *******注意模板并非一个视图,模板是由视图定义配置出来的,并被其管理着*******
 77           // 模板是用一种声明的方式指定部分甚至全部的视图对象
 78           fragment += `<li>${this.todos[i].text}</li>`
 79         }
 80         this.todoList.innerHTML = fragment
 81       },
 82 
 83       // render()函数做为一个订阅者的回调函数,数据的变化会反馈到模型 store
 84       // 换句话说:视图经过观察者模式,观察模型 store,当模型发生改变,触发视图更新
 85       render() {
 86         this.view()
 87 
 88         /**
 89          * 这里须要特别提一下,按照 MVC 原则这里本不该该出现下面的代码的
 90          * 由于业务逻辑关系(我本地存储使用的是同一个key值,再次写入数据会覆盖原来的数据,),
 91          * 因此必须通知模型 M 保存数据, V 层处理了不应它处理的逻辑,致使 M 与 V 耦合
 92          *
 93          * 解决办法是:将其抽象出来编写一个 视图助手 helper
 94          */
 95         Utils.store(ADD_KEY, this.todos)
 96       },
 97 
 98       getItemIndex(item) {
 99         let itemIndex
100         if (item.target.tagName.toLowerCase() === 'li') {
101           let arr = Array.prototype.slice.call(this.todoListItem)
102           let index = arr.indexOf(item.target)
103           return itemIndex = index
104         }
105       },
106 
107       add(e) {
108         let id = Number(new Date())
109         let text = this.contentBox.value
110         let addTodo = new Todo(id, text)
111         this.todos.unshift(addTodo) // 模型发生改变
112         this.render()  // 当模型发生改变,触发视图更新
113       },
114 
115       remove(item) {
116         let index = this.getItemIndex(item)
117         this.todos.splice(index, 1)
118         this.render()
119       }
120     }
121 
122     App.init()
123   })()
124 </script>
125 </body>
126 </html>

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

 

mvc模式的优势

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

 

mvc模式的弊端

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

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

 

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

相关文章
相关标签/搜索