简单谈谈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,模拟一个存储数据对象的后台模型。

  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模式的弊端

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

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

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

时间: 2024-07-30 10:18:52

简单谈谈js中的MVC的相关文章

谈谈JS中的面向对象

请先看看下面这段代 1 <script src="jquery.js"></script> 2 <script type="text/javascript"> 3 /** 4 * Object.create() 最近才添加进了ECMAScript第5版规范,有些浏览器不支持 5 * 这里模拟一个Object.create方法解决兼容性问题 6 * Object.create : 该方法只有一个参数,即原型对象,返回一个新对象 7

浅谈js中的MVC

MVC是什么? MVC是一种架构模式,它将应用抽象为3个部分:模型(数据).视图.控制器(分发器) 本文将用一个经典的例子todoList来展开 一个事件发生的过程(通信单向流动): 1.用户在视图V上与应用程序交互 2.控制器C触发相应的事件,要求模型M改变状态(读写数据) 3.模型M将数据发送到视图V,更新数据,展现给用户 在js的传统开发模式中,大多基于事件驱动的: 1.hash驱动 2.DOM事件,用来驱动视图 3.模型事件(业务模型事件和数据模型事件),用来驱动模型和模型结合 所以js

30 行代码实现 JS 中的 MVC

一连串的名字走马观花式的出现和更迭,它们中一些已经渐渐淡出了大家的视野,一些还在迅速茁壮成长,一些则已经在特定的生态环境中独当一面舍我其谁.但不论如何,MVC已经并将持续深刻地影响前端工程师们的思维方式和工作方法. 很多讲解MVC的例子都从一个具体的框架的某个概念入手,比如Backbone的collection或AngularJS中model,这当然不失为一个好办法.但框架之所以是框架,而不是类库(jQuery)或者工具集(Underscore),就是因为它们的背后有着众多优秀的设计理念和最佳实

简单聊一聊JS中的循环引用及问题

本文主要从 JS 中为什么会出现循环引用,垃圾回收策略中引用计数为什么有很大的问题,以及循环引用时的对象在使用 JSON.stringify 时为什么会报错,怎样解决这个问题简单谈谈自己的一些理解. 1. 什么是循环引用 当对象 1 中的某个属性指向对象 2,对象 2 中的某个属性指向对象 1 就会出现循环引用,(当然不止这一种情况,不过原理是一样的)下面通过代码和内存示意图来说明一下. function circularReference() { let obj1 = { }; let obj

简单谈谈JVM中的GC(下)

在系列的最后,简单谈谈一些会有坑的JVM参数配置,以避免大家再多次踩坑 -XX:+DisableExplicitGC 很多的JVM标准配置中都有该选项,那么它究竟是干嘛的? 它会让System.gc()变成一次空调用,并不会真的发生一次Full Gc.除此以外,它还能避免第三方库定时引发的Full Gc(没错,说的就是RMI机制),看来很美好,对不对? 但有一种情况:应用本身GC正常,很久都不会Full Gc,但堆外内存增长很快,并且JVM启用了-XX:+DisableExplicitGC.你就

简单谈谈JVM中的GC(中)

书接上文,在了解JVM的分代模型后,接着来简单聊聊JVM中GC算法和不同的GC收集器[求关注] GC回收算法 一个GC回收算法通常会做这么几件事: 1.遍历内存,找到被引用的对象 2.清理掉这些未被标记对象的内存 3.被清理掉的内存放回内存中,供其他地方使用 上文也提及过,目前JVM中的搜索引用对象是用的根搜索方式,再重复引用下: 所有的Java对象构成一颗近似"搜索树"的结构,有一个root根节点,每次从root出发向下搜索,当整个树遍历完成后,那些不在其中的变量则视为"垃

谈谈 js中的几种模式 (一)

今天看了<JavaScript 高级程序设计>(第三版)这本书,颇有收获,总想写点什么,只恨自己菜鸟一只写不出什么真知灼见,只能......好了废话不多说,开篇了. 大家都知道在js中可以用Object构造函数和对象字面量这 //利用Object构造函数创建对象 var person=new Object(); person.name="DJL"; person.age=22; //利用对象字面量创建对象 var person2={ name:"DJL"

谈谈JS中的原型

不知道大家对JS中的原型理解的怎么样,我想如果大家对JS中的原型对象以及prototype属性十分熟悉的话对后面原型链以及继承的理解会十分的容易,这里想和大家分享自己对其的理解,请先看下面这段代码O(∩_∩)O~~ 1 function Person(){ 2 } 3 Person.prototype.name = "jingzi"; 4 Person.prototype.age = 20; 5 Person.prototype.sayName = function(){ 6 aler

谈谈js中for in 需要注意的地方

js中for in 可以遍历对象或数组的显性属性,也就是说我们自己定义的属性是可以遍历的,那些原型上默认已有的属性,例如:Object.prototype.toString.Object.prototype.hasOwnProperty 是遍历不出来的. for in 的基本规则如上,不过还有“坑”的地方需要我们注意: 1.for in循环出的值不一定是按顺序的.代码如下: var b = {3:1,42:2,11:3} for( var key in b ){ alert( b[key] )