网站开发 站长统计,2345网址导航手机版,wordpress 界面插件,024 网站推广前言我想用 model-view-controller 架构模式在纯 JavaScript 中写一个简单的程序#xff0c;于是我这样做了。希望它可以帮你理解 MVC#xff0c;因为当你刚开始接触它时#xff0c;它是一个难以理解的概念。我做了这个todo应用程序#xff0c;这是一个简单小巧的浏览器应用…前言我想用 model-view-controller 架构模式在纯 JavaScript 中写一个简单的程序于是我这样做了。希望它可以帮你理解 MVC因为当你刚开始接触它时它是一个难以理解的概念。我做了这个todo应用程序这是一个简单小巧的浏览器应用允许你对待办事项进行CRUD创建读取更新和删除操作。它只包含 index.html、style.css和script.js 三个文件非常简单无需任何依赖和框架。先决条件基本的 JavaScript 和 HTML 知识熟悉最新的 JavaScript 语法目标用纯 JavaScript 在浏览器中创建一个 todo 应用程序并熟悉MVC和 OOP——面向对象编程的概念。查看程序的演示查看程序的源代码注意由于此程序使用了最新的 JavaScript 功能ES2017因此在某些浏览器如 Safari上无法用 Babel 编译为向后兼容的 JavaScript 语法。什么是 MVCMVC 是一种非常受欢迎组织代码的模式。Model模型 - 管理程序的数据View视图 - 模型的直观表示Controller控制器 - 链接用户和系统模型是数据。在这个 todo 程序中这将是实际的待办事项以及将添加、编辑或删除它们的方法。视图是数据的显示方式。在这个程序中是 DOM 和 CSS 中呈现的 HTML。控制器用来连接模型和视图。它需要用户输入例如单击或键入并处理用户交互的回调。模型永远不会触及视图。视图永远不会触及模型。控制器用来连接它们。我想提一下为一个简单的 todo 程序做 MVC 实际上是一大堆样板。如果这是你想要创建的程序并且创建了整个系统那真的会让事情变得过于复杂。关键是要尝试在较小的层面上理解它。初始设置这将是一个完全用 JavaScript 写的程序这意味着一切都将通过 JavaScript 处理HTML 将只包含根元素。index.html!DOCTYPE html
html langenheadmeta charsetutf-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /meta http-equivX-UA-Compatible contentieedge /titleTodo App/titlelink relstylesheet hrefstyle.css //headbodydiv idroot/divscript srcscript.js/script/body
/html我写了一小部分 CSS 只是为了让它看起来可以接受你可以找到这个文件并保存到 style.css 。我不打算再写CSS了因为它不是本文的重点。好的现在我们有了HTML和CSS下面该开始编写程序了。入门我会使这个教程简单易懂使你轻松了解哪个类属于 MVC 的哪个部分。我将创建一个 Model 类View 类和 Controller 类。该程序将是控制器的实例。如果你不熟悉类的工作方式请阅读了解JavaScript中的类。class Model {constructor() {}
}class View {constructor() {}
}class Controller {constructor(model, view) {this.model modelthis.view view}
}const app new Controller(new Model(), new View())
模型让我们先关注模型因为它是三个部分中最简单的一个。它不涉及任何事件或 DOM 操作。它只是存储和修改数据。//模型
class Model {constructor() {// The state of the model, an array of todo objects, prepopulated with some datathis.todos [{ id: 1, text: Run a marathon, complete: false },{ id: 2, text: Plant a garden, complete: false },]}// Append a todo to the todos arrayaddTodo(todo) {this.todos [...this.todos, todo]}// Map through all todos, and replace the text of the todo with the specified ideditTodo(id, updatedText) {this.todos this.todos.map(todo todo.id id ? { id: todo.id, text: updatedText, complete: todo.complete } : todo)}// Filter a todo out of the array by iddeleteTodo(id) {this.todos this.todos.filter(todo todo.id ! id)}// Flip the complete boolean on the specified todotoggleTodo(id) {this.todos this.todos.map(todo todo.id id ? { id: todo.id, text: todo.text, complete: !todo.complete } : todo)}
}
我们定义了 addTodo、editTodo、deleteTodo和toggleTodo。这些都应该是一目了然的add 添加到数组edit 找到 todo 的 id 进行编辑和替换delete 过滤数组中的todo并切换切换 complete 布尔属性。由于我们在浏览器中执行此操作并且可以从窗口全局访问因此你可以轻松地测试这些内容输入以下内容app.model.addTodo({ id: 3, text: Take a nap, complete: false })
将向列表中添加一个待办事项你可以查看 app.model.todos 的内容。这对于现在的模型来说已经足够了。最后我们会将待办事项存储在 local storage 中以使其成为半永久性的但现在只要刷新页面todo 就会刷新。我们可以看到该模型仅处理并修改实际数据。它不理解或不知道输入 —— 正在修改它或输出 —— 最终会显示什么。这时如果你通过控制台手动输入所有操作并在控制台中查看输出就可以获得功能完善的 CRUD 程序所需的一切。视图我们将通过操纵 DOM —— 文档对象模型来创建视图。由于没有 React 的 JSX 或模板语言的帮助在普通的 JavaScript 中执行此操作因此它将是冗长和丑陋的但这是直接操纵 DOM 的本质。控制器和模型都不应该知道关于 DOM、HTML元素、CSS 或其中任何内容的信息。任何与之相关的内容都应该放在视图中。如果你不熟悉 DOM 或 DOM 与 HTML 源代码之间有什么不同请阅读DOM简介。要做的第一件事就是创建辅助方法来检索并创建元素。//视图
class View {constructor() {}// Create an element with an optional CSS classcreateElement(tag, className) {const element document.createElement(tag)if (className) element.classList.add(className)return element}// Retrieve an element from the DOMgetElement(selector) {const element document.querySelector(selector)return element}
}
到目前为止还挺好。接着在构造函数中我将为视图设置需要的所有东西应用程序的根元素 - #root标题 h1一个表单输入框和提交按钮用于添加待办事项 - form, input, button待办事项清单 - ul我将在构造函数中创建所有变量以便可以轻松地引用它们。//视图
class View {constructor() {// The root elementthis.app this.getElement(#root)// The title of the appthis.title this.createElement(h1)this.title.textContent Todos// The form, with a [typetext] input, and a submit buttonthis.form this.createElement(form)this.input this.createElement(input)this.input.type textthis.input.placeholder Add todothis.input.name todothis.submitButton this.createElement(button)this.submitButton.textContent Submit// The visual representation of the todo listthis.todoList this.createElement(ul, todo-list)// Append the input and submit button to the formthis.form.append(this.input, this.submitButton)// Append the title, form, and todo list to the appthis.app.append(this.title, this.form, this.todoList)}// ...
}
现在将设置不会被更改的视图部分。另外两个小东西输入new todo值的 getter 和 resetter。// 视图
get todoText() {return this.input.value
}resetInput() {this.input.value
}
现在所有设置都已完成。最复杂的部分是显示待办事项列表这是每次对待办事项进行修改时将被更改的部分。//视图
displayTodos(todos) {// ...
}
displayTodos 方法将创建待办事项列表所包含的 ul 和 li 并显示它们。每次修改、添加或删除 todo 时都会使用模型中的 todos 再次调用 displayTodos 方法重置列表并重新显示它们。这将使视图与模型的状态保持同步。我们要做的第一件事就是每次调用时删除所有 todo 节点。然后检查是否存在待办事项。如果不这样做我们将会得到一个空的列表消息。// 视图
// Delete all nodes
while (this.todoList.firstChild) {this.todoList.removeChild(this.todoList.firstChild)
}// Show default message
if (todos.length 0) {const p this.createElement(p)p.textContent Nothing to do! Add a task?this.todoList.append(p)
} else {// ...
}
现在循环遍历待办事项并为每个现有待办事项显示复选框、span 和删除按钮。// 视图
else {// Create todo item nodes for each todo in statetodos.forEach(todo {const li this.createElement(li)li.id todo.id// Each todo item will have a checkbox you can toggleconst checkbox this.createElement(input)checkbox.type checkboxcheckbox.checked todo.complete// The todo item text will be in a contenteditable spanconst span this.createElement(span)span.contentEditable truespan.classList.add(editable)// If the todo is complete, it will have a strikethroughif (todo.complete) {const strike this.createElement(s)strike.textContent todo.textspan.append(strike)} else {// Otherwise just display the textspan.textContent todo.text}// The todos will also have a delete buttonconst deleteButton this.createElement(button, delete)deleteButton.textContent Deleteli.append(checkbox, span, deleteButton)// Append nodes to the todo listthis.todoList.append(li)})
}
现在设置视图及模型。我们只是没有办法连接它们因为现在还没有事件监视用户进行输入也没有处理这种事件的输出的 handle。控制台仍然作为临时控制器存在你可以通过它添加和删除待办事项。控制器最后控制器是模型数据和视图用户看到的内容之间的链接。这是我们到目前为止控制器中的内容。//控制器
class Controller {constructor(model, view) {this.model modelthis.view view}
}
在视图和模型之间的第一个链接是创建一个每次 todo 更改时调用 displayTodos 的方法。我们也可以在 constructor 中调用它一次来显示初始的 todos如果有的话。//控制器
class Controller {constructor(model, view) {this.model modelthis.view view// Display initial todosthis.onTodoListChanged(this.model.todos)}onTodoListChanged todos {this.view.displayTodos(todos)}
}
控制器将在触发后处理事件。当你提交新的待办事项、单击删除按钮或单击待办事项的复选框时将触发一个事件。视图必须侦听这些事件因为它们是视图的用户输入它会将响应事件所要做的工作分配给控制器。我们将为事件创建 handler。首先提交一个 handleAddTodo 事件当我们创建的待办事项输入表单被提交时可以通过按 Enter 键或单击“提交”按钮来触发。这是一个 submit 事件。回到视图中我们将 this.input.value 的 getter 作为 get todoText。要确保输入不能为空然后我们将创建带有 id、text 并且 complete 值为 false 的 todo。将 todo 添加到模型中然后重置输入框。// 控制器
// Handle submit event for adding a todo
handleAddTodo event {event.preventDefault()if (this.view.todoText) {const todo {id: this.model.todos.length 0 ? this.model.todos[this.model.todos.length - 1].id 1 : 1,text: this.view.todoText,complete: false,}this.model.addTodo(todo)this.view.resetInput()}
}
删除 todo 的操作类似。它将响应删除按钮上的 click 事件。删除按钮的父元素是 todo li 本身它附有相应的 id。我们需要将该数据发送给正确的模型方法。// 控制器
// Handle click event for deleting a todo
handleDeleteTodo event {if (event.target.className delete) {const id parseInt(event.target.parentElement.id)this.model.deleteTodo(id)}
}
在 JavaScript 中当你单击复选框来切换它时会发出 change 事件。按照处理单击删除按钮的方式处理此方法并调用模型方法。// 控制器
// Handle change event for toggling a todo
handleToggle event {if (event.target.type checkbox) {const id parseInt(event.target.parentElement.id)this.model.toggleTodo(id)}
}
这些控制器方法有点乱 - 理想情况下它们不应该处理任何逻辑而是应该简单地调用模型。设置事件监听器现在我们有了这三个 handler 但控制器仍然不知道应该什么时候调用它们。必须把事件侦听器放在视图中的 DOM 元素上。我们将回复表单上的submit 事件以及 todo 列表上的 click 和 change事件。在 View 中添加一个 bindEvents 方法该方法将调用这些事件。// 视图
bindEvents(controller) {this.form.addEventListener(submit, controller.handleAddTodo)this.todoList.addEventListener(click, controller.handleDeleteTodo)this.todoList.addEventListener(change, controller.handleToggle)
}
接着把侦听事件的方法绑定到视图。在 Controller 的 constructor 中调用 bindEvents 并传递控制器的this 上下文。在所有句柄事件上都用了箭头函数。这允许我们可以用控制器的 this 上下文从视图中调用它们。如果不用箭头函数我们将不得不手动去绑定它们如 controller.handleAddTodo.bind(this)。// 控制器
this.view.bindEvents(this)
现在当指定的元素发生submit、click 或 change 事件时将会调用相应的 handler。响应模型中的回调我们还遗漏了一些东西事件正在侦听handler 被调用但是没有任何反应。这是因为模型不知道视图应该更新并且不知道如何更新视图。我们在视图上有 displayTodos 方法来解决这个问题但如前所述模型和视图不应该彼此了解。就像侦听事件一样模型应该回到控制器让它知道发生了什么。我们已经在控制器上创建了 onTodoListChanged 方法来处理这个问题接下来只需让模型知道它。我们将它绑定到模型就像对视图上的 handler 所做的一样。在模型中为 onTodoListChanged 添加 bindEvents。// 模型
bindEvents(controller) {this.onTodoListChanged controller.onTodoListChanged
}
在控制器中发送 this 上下文。// 控制器
constructor() {// ...this.model.bindEvents(this)this.view.bindEvents(this)
}
现在在模型中的每个方法之后你将调用 onTodoListChanged 回调。在更复杂的程序中可能对不同的事件有不同的回调但在这个简单的待办事项程序中我们可以在所有方法之间共享一个回调。//模型
addTodo(todo) {this.todos [...this.todos, todo]this.onTodoListChanged(this.todos)
}
添加 local storage这时程序的大部分都已完成所有概念都已经演示过了。我们可以通过将数据保存在浏览器的 local storage 中来对其进行持久化。如果你不了解 local storage 的工作原理请阅读如何使用JavaScript local storage。现在我们可以将待办事项的初始值设置为本地存储或空数组。// 模型
class Model {constructor() {this.todos JSON.parse(localStorage.getItem(todos)) || []}
}
然后创建一个 update 函数来更新 localStorage 的值。//模型
update() {localStorage.setItem(todos, JSON.stringify(this.todos))
}
每次更改 this.todos 后我们都可以调用它。//模型
addTodo(todo) {this.todos [...this.todos, todo]this.update()this.onTodoListChanged(this.todos)
}
添加实时编辑功能这个难题的最后一部分是编辑现有待办事项的能力。编辑总是比添加或删除更棘手。我想简化它不需要编辑按钮或用 input 或任何东西替换 span。我们也不想每输入一个字母都调用 editTodo因为它会重新渲染整个待办事项列表UI。我决定在控制器上创建一个方法用新的编辑值更新临时状态变量另一个方法调用模型中的 editTodo 方法。//控制器
constructor() {// ...this.temporaryEditValue
}// Update temporary state
handleEditTodo event {if (event.target.className editable) {this.temporaryEditValue event.target.innerText}
}// Send the completed value to the model
handleEditTodoComplete event {if (this.temporaryEditValue) {const id parseInt(event.target.parentElement.id)this.model.editTodo(id, this.temporaryEditValue)this.temporaryEditValue }
}
我承认这个解决方案有点乱因为 temporaryEditValue 变量在技术上应该在视图中而不是在控制器中因为它是与视图相关的状态。现在我们可以将这些添加到视图的事件侦听器中。当你在 contenteditable 元素输入时input 事件会被触发离开contenteditable元素时focusout 会触发。//视图
bindEvents(controller) {this.form.addEventListener(submit, controller.handleAddTodo)this.todoList.addEventListener(click, controller.handleDeleteTodo)this.todoList.addEventListener(input, controller.handleEditTodo)this.todoList.addEventListener(focusout, controller.handleEditTodoComplete)this.todoList.addEventListener(change, controller.handleToggle)
}
现在当你单击任何待办事项时将进入“编辑”模式这将会更新临时状态变量当选中或单击待办事项时将会保存在模型中并重置临时状态。contenteditable 解决方案很快得到实施。在程序中使用 contenteditable 时需要考虑各种问题我在这里写过许多内容。总结现在你拥有了一个用纯 JavaScript 写的 todo 程序它演示了模型 - 视图 - 控制器体系结构的概念。以下是演示和源代码的链接。查看程序的演示查看程序的源代码我希望本教程能帮你理解 MVC。使用这种松散耦合的模式可以为程序添加大量的样板和抽象同时它也是一种开发人员熟悉的模式是一个通常用于许多框架的重要概念。原文出处思否原文作者疯狂的技术宅原文链接https://segmentfault.com/a/1190000020007033