老生常谈js中的MVC
mvc是什么?
mvc是一种架构模式,它将应用抽象为3个部分:模型(数据)、视图、控制器(分发器)。
本文将用一个经典的例子todolist来展开(代码在最后)。
一个事件发生的过程(通信单向流动):
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就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
上一篇: Context你真的了解吗?
下一篇: 详解VueJS 数据驱动和依赖追踪分析