视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
老生常谈js中的MVC
2020-11-27 22:34:14 责编:小采
文档


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就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

下载本文
显示全文
专题