老生常谈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模式的优点

以上是 老生常谈js中的MVC 的全部内容, 来源链接: utcz.com/z/348360.html

回到顶部