初次翻译,翻译的不好,还请见谅

JavaScript中最好的一部分之一,也可能是最糟糕的。 在HTML文档的头部添加一个开始和结束脚本标记,并在其中引入一些意大利面条式的代码,毫无疑问这是一种过分简单化的能力。 瞧! 有用! 真的吗? 你问什么是意大利面条式的代码? 意大利面条式的代码是一个直白的术语,指的是那些代码凌乱,控制结构复杂,并且这种代码到处都是。 它几乎不可能维护和调试,通常具有非常差的结构,并且容易出错。 网页设计和开发已经具有足够的挑战性,不要让你的工作变得更加困难!

那么你怎样才能结束编写这样的代码呢? 我认为,你只有两个选择。 再次声明,这只是我个人的意见。 第一个,你可以使用JavaScript框架。第二个,学习如何使用某种模式或结构编写JavaScript代码。 MVC,MVP和MVVM是一些常见模式,有助于指导开发人员创建抽象和解耦的解决方案。 这些模式之间的主要区别在于如何处理数据层,表示层和应用逻辑。 在这篇博文中,您将专注于MVC或模型 - 视图 - 控制器模式。

模型(数据层) - 这是为您的应用程序存储数据的位置。 模型与视图和控制器分离, 每当模型发生变化时,它将通知其观察者使用事件分派器发生了更改。 你会很快阅读关于事件调度程序。 在您将要构建的待办事项列表应用程序中,模型将保存任务列表,并负责对每个任务对象执行的任何操作。

视图(表示层) - 这一部分可以访问DOM,并负责事件处理程序,如:click,onmouseover,onmouseout等。视图也负责HTML的呈现。 在待办事项列表应用程序中,视图将负责向用户显示任务列表。 但是,每当用户通过输入新任务时,视图将使用事件分派器通知控制器,然后控制器将更新模型。 这能够让视图与模型快速解耦。

控制器(应用逻辑) - 控制器是模型和视图之间的粘合剂。 控制器处理和响应由模型或视图设置的事件。 它在用户操纵视图时更新模型,并且还可以在模型更改时更新视图。 在本教程中,您将通过分派事件直接从模型更新视图。 但是,不管哪一种方式都是可以的。 在待办事项列表应用程序中,当用户单击添加任务按钮时,点击将传递到控制器,控制器通过向其添加任务来修改模型。 任何其他决策或逻辑也可以在这里执行,例如:使用HTML本地存储保存数据,对数据库/服务器进行异步保存等等。

事件分派器 - 事件分派器是一个对象,该对象允许您将无限数量的函数/方法附加到它身上。 当你最终调用该事件对象的notify方法时,你所附加到该事件的每个方法都会被运行。 你将会在下面的代码中看到它被多次使用。

现在,您已经基本了解了为什么应该使用JavaScript设计模式,以及MVC架构是什么,现在您可以开始构建应用程序。 下面将简单介绍这个教程是怎么工作的。 首先,我将向您呈现代码。 这将给你机会检查和通过它。 第二,我将详细介绍代码使用的一些核心概念,并尝试阐明任何可能会产生混淆的潜在朦胧或灰色区域。 总而言之,我将向您展示最终组件的几个截图。 为了充分利用这个教程,我建议你好好看几遍,直到你能够自己编写这些代码。 本教程假设你有HTML和JavaScript经验。 如果你是JavaScript新手,我建议你快速看一下http://www.w3schools.com/Js/。 您还可以在GitHub上找到该项目https://github.com/joshcrawmer4/Javascript-MVC-App。 开始吧!

开始写一些代码

建立index.html文件:

<!doctype html>
<html> <head>
<title>Javascript MVC</title>
<script src="https://code.jquery.com/jquery-2.2.3.min.js" ></script> <style> </style> </head> <body> <div class="js-container"> <input type="text" class="js-task-textbox">
<input type="button" class="js-add-task-button" value="Add Task"> <div class="js-tasks-container"></div>
<!-- end tasks -->
<input type="button" class="js-complete-task-button" value="Complete Tasks">
<input type="button" class="js-delete-task-button" value="Delete Tasks"> </div>
<!-- end js-container --> <script src="EventDispatcher.js"></script>
<script src="TaskModel.js"></script>
<script src="TaskView.js"></script>
<script src="TaskController.js"></script>
<script src="App.js"></script> </body> </html>

接下来,创建EventDispatcher.js文件:

var Event = function (sender) {
this._sender = sender;
this._listeners = [];
} Event.prototype = { attach: function (listener) {
this._listeners.push(listener);
}, notify: function (args) {
for (var i = 0; i < this._listeners.length; i += 1) {
this._listeners[i](this._sender, args);
}
} };

下一步,创建TaskModel.js文件:

 var TaskModel = function () {
this.tasks = [];
this.selectedTasks = [];
this.addTaskEvent = new Event(this);
this.setTasksAsCompletedEvent = new Event(this);
this.deleteTasksEvent = new Event(this); }; TaskModel.prototype = { addTask: function (task) {
this.tasks.push({
taskName: task,
taskStatus: 'uncompleted'
});
this.addTaskEvent.notify();
}, getTasks: function () {
return this.tasks;
}, setSelectedTask: function (taskIndex) {
this.selectedTasks.push(taskIndex);
}, unselectTask: function (taskIndex) {
this.selectedTasks.splice(taskIndex, 1);
}, setTasksAsCompleted: function () {
var selectedTasks = this.selectedTasks;
for (var index in selectedTasks) {
this.tasks[selectedTasks[index]].taskStatus = 'completed';
} this.setTasksAsCompletedEvent.notify(); this.selectedTasks = []; }, deleteTasks: function () {
var selectedTasks = this.selectedTasks.sort(); for (var i = selectedTasks.length - 1; i >= 0; i--) {
this.tasks.splice(this.selectedTasks[i], 1);
} // clear the selected tasks
this.selectedTasks = []; this.deleteTasksEvent.notify(); } };

下一步,创建一个TaskView.js文件:

var TaskView = function (model) {
this.model = model;
this.addTaskEvent = new Event(this);
this.selectTaskEvent = new Event(this);
this.unselectTaskEvent = new Event(this);
this.completeTaskEvent = new Event(this);
this.deleteTaskEvent = new Event(this); this.init();
}; TaskView.prototype = { init: function () {
this.createChildren()
.setupHandlers()
.enable();
}, createChildren: function () {
// cache the document object
this.$container = $('.js-container');
this.$addTaskButton = this.$container.find('.js-add-task-button');
this.$taskTextBox = this.$container.find('.js-task-textbox');
this.$tasksContainer = this.$container.find('.js-tasks-container'); return this;
}, setupHandlers: function () { this.addTaskButtonHandler = this.addTaskButton.bind(this);
this.selectOrUnselectTaskHandler = this.selectOrUnselectTask.bind(this);
this.completeTaskButtonHandler = this.completeTaskButton.bind(this);
this.deleteTaskButtonHandler = this.deleteTaskButton.bind(this); /**
Handlers from Event Dispatcher
*/
this.addTaskHandler = this.addTask.bind(this);
this.clearTaskTextBoxHandler = this.clearTaskTextBox.bind(this);
this.setTasksAsCompletedHandler = this.setTasksAsCompleted.bind(this);
this.deleteTasksHandler = this.deleteTasks.bind(this); return this;
}, enable: function () { this.$addTaskButton.click(this.addTaskButtonHandler);
this.$container.on('click', '.js-task', this.selectOrUnselectTaskHandler);
this.$container.on('click', '.js-complete-task-button', this.completeTaskButtonHandler);
this.$container.on('click', '.js-delete-task-button', this.deleteTaskButtonHandler); /**
* Event Dispatcher
*/
this.model.addTaskEvent.attach(this.addTaskHandler);
this.model.addTaskEvent.attach(this.clearTaskTextBoxHandler);
this.model.setTasksAsCompletedEvent.attach(this.setTasksAsCompletedHandler);
this.model.deleteTasksEvent.attach(this.deleteTasksHandler); return this;
}, addTaskButton: function () {
this.addTaskEvent.notify({
task: this.$taskTextBox.val()
});
}, completeTaskButton: function () {
this.completeTaskEvent.notify();
}, deleteTaskButton: function () {
this.deleteTaskEvent.notify();
}, selectOrUnselectTask: function (event) { var taskIndex = $(event.target).attr("data-index"); if ($(event.target).attr('data-task-selected') == 'false') {
$(event.target).attr('data-task-selected', true);
this.selectTaskEvent.notify({
taskIndex: taskIndex
});
} else {
$(event.target).attr('data-task-selected', false);
this.unselectTaskEvent.notify({
taskIndex: taskIndex
});
} }, show: function () {
this.buildList();
}, buildList: function () {
var tasks = this.model.getTasks();
var html = "";
var $tasksContainer = this.$tasksContainer; $tasksContainer.html(''); var index = 0;
for (var task in tasks) { if (tasks[task].taskStatus == 'completed') {
html = "<div style='color:green; text-decoration:line-through'>";
} else {
html = "<div>";
} $tasksContainer.append(html + "<label><input type='checkbox' class='js-task' data-index='" + index + "' data-task-selected='false'>" + tasks[task].taskName + "</label></div>"); index++;
} }, /* -------------------- Handlers From Event Dispatcher ----------------- */ clearTaskTextBox: function () {
this.$taskTextBox.val('');
}, addTask: function () {
this.show();
}, setTasksAsCompleted: function () {
this.show(); }, deleteTasks: function () {
this.show(); } /* -------------------- End Handlers From Event Dispatcher ----------------- */ };

下一步创建一个TaskController.js文件:

var TaskController = function (model, view) {
this.model = model;
this.view = view; this.init();
}; TaskController.prototype = { init: function () {
this.createChildren()
.setupHandlers()
.enable();
}, createChildren: function () {
// no need to create children inside the controller
// this is a job for the view
// you could all as well leave this function out
return this;
}, setupHandlers: function () {

     // 为什么绑定this没理解,不绑定就会出现this.model未定义,是不是在赋值的时候出现this的指向的变化????
this.addTaskHandler = this.addTask.bind(this);
this.selectTaskHandler = this.selectTask.bind(this);
this.unselectTaskHandler = this.unselectTask.bind(this);
this.completeTaskHandler = this.completeTask.bind(this);
this.deleteTaskHandler = this.deleteTask.bind(this);
return this;
}, enable: function () { this.view.addTaskEvent.attach(this.addTaskHandler);
this.view.completeTaskEvent.attach(this.completeTaskHandler);
this.view.deleteTaskEvent.attach(this.deleteTaskHandler);
this.view.selectTaskEvent.attach(this.selectTaskHandler);
this.view.unselectTaskEvent.attach(this.unselectTaskHandler); return this;
}, addTask: function (sender, args) {
this.model.addTask(args.task);
}, selectTask: function (sender, args) {
this.model.setSelectedTask(args.taskIndex);
}, unselectTask: function (sender, args) {
this.model.unselectTask(args.taskIndex);
}, completeTask: function () {
this.model.setTasksAsCompleted();
}, deleteTask: function () {
this.model.deleteTasks();
} };

下一步创建下面的代码一个App.js文件:

 $(function () {
var model = new TaskModel(),
view = new TaskView(model),
controller = new TaskController(model, view);
});

概述

好了,那么每个文件都在做些什么呢!

index.html —— 没有多少新东西。 包括jQuery CDN,设置一些基本的HTML,加载JavaScript文件。 哇! 这些都很简单!

EventDispatcher.js —— 这是一个类,有两个方法,attach()和 notify()。 在 attach()方法 接受一个函数作为参数。 只要你想, 你可以调用attach()n 次。这个函数(这里指参数)的功能由你来决定。 一旦您调用了该Event对象的notify方法,附加到该事件的每个函数都将被运行。

TaskModel.js —— 这个类有一些基本方法来添加和删除实际任务的数组对象。 在构造函数中设置三个事件对象,当一个任务被添加后,标记为完成,或删除之后,允许模型调用每个事件对象的notify()方法。 转而将响应传递给视图来重新呈现HTML以显示更新的任务列表。 这里主要是模型将响应传递给视图。模型 --> 视图

TaskView.js -这是该项目最大的文件,并可能已被抽象成多个视图。 但是为了简单起见,我把一切都放到一个类中。 构造函数设置了五个Event对象。 这可以调用每个事件对象的notify()方法,从而传递响应到控制器。 接下来,你会看到构造函数调用 init()方法。 此 init 方法使用方法链建立此类的骨架。

createChildren()—— 在 this.$container 变量上缓存 $('.js-container') 这个DOM元素,后面需要访问具体的元素,它需要find()。 这只是一个性能事件,并允许jQuery从变量中拉取任何元素,而不是重新搜索/爬取DOM。 注意 reutrn this 的使用。 这是使得方法链存在 init 的主要原因。

setupHandlers() —— 这部分刚开始可能你会有点迷糊。这个方法用来设置 事件处理和改变内部 的 this 指向。 基本上,只要你碰到一个JavaScript事件处理程序,并打算在回调函数使用 this 这个关键字,那么这会引用事件发生的实际对象或元素。 这在很多情况下是不可取的,如MVC情况下,当你希望 this 引用实际类的本身。 在这里,你可以在回调函数上使用bind(this)。 这会 改变 this 作用域,而不是指向初始化该事件的对象或元素。 Mozilla基金会有一个很好的教程讲解如何使用 scope bound functions 。(这段话主要讲 this ,以及如何改变this的指向)

enable() —— 此方法设置的任何DOM事件,以及附加到 event Dispatcher 的任何方法都由模型来创建。 看下面这行代码:

this.model.addTaskEvent.attach(this.addTaskHandler);

这里到底发生了什么? 当模式调用 addTaskEvent.notify(), 视图将运行this.addTaskHandler()方法。 太棒了! 你实际上看到了 EventDispatcher 是怎样工作的! 这允许你的类彼此交谈,同时保持解耦。 然后,addTaskHandler()方法调用 show() 方法, show() 方法反过来调用 buildList()方法,最后重新呈现 HTML 列表。

那么,你应该从这些中得到什么? 基本上,一旦模型将数据传递给视图,视图将重新呈现HTML以显示最新的任务对象。 此外,每当用户通过DOM事件操纵视图时,视图会将响应传递给控制器​​。 视图不直接与模型一起工作。

TaskController.js —— 该类坐在视图和模型中间,充当粘合剂。 它能够让你的模型和视图轻松解耦。无论何时,当视图调用EventDispatcher时,控制器通过监听然后更新模型。 此外,这个文件中的所有方法声明和View和Model中看起来很像。

App.js —— 此文件是负责启动的应用程序。 在这里创建 Model-View-Controller 的一个实例。

结论

作为一名开发人员,您必须始终努力保持最新状态。 无论是参与GitHub上的项目,使用新语言,还是学习设计模式和原则。 但有一点是肯定的,你必须总是向前迈进。 现在你有一个基本的概述如何编写JavaScript的MVC方式,你看看如何可能结束编写意大利面条式的代码,以及如何开始编写更加整洁,更易于维护的代码。 随时在GitHub上与我联系,或有任何问题可发表在评论区。

翻译结束,个人想法:

个人分析

估计很多人,看到程序也是很难理解。我就添加下个人的解释好了

  • 在App.js里运行的时候,在创建view的实例的时候,会添加相应的事件处理函数,比如事件绑定,往model里面添加事件处理函数;
  • 接着开始创建controller的实例,值得注意的事,view中的model和controller中的model指向的是同一个。
  • 然后也开始添加相应的时间处理,比如,往view中添加相应的事件最后就是显示
  • 显示之后,用户点击视图的添加按钮,会调用相应的处理器,获取用户输入的数据。然后会告诉控制器,
  • 控制器呢会让model把这个数据添加进去,添加完成后,model会告诉view 我添加好了。这时候又会调用相应的处理器,最终显示
  • 其他类似

作者如果能在代码加一些注释就好了,不过作者的写法值得我学习,条理逻辑分的很清楚。

当然了,即使看懂了,你还要做的是,如果是你,你能写出来吗?也就是我们要分析作者是如何考虑的。

首先是MVC之间的通信是采用了观察者模式。

其次,我们就要分析显示的内容,以及相应的操作。

这是一个to-do-list,很明显就要有添加事件,删除事件,选中事件,完成事件等等。

一句话,就是你要把你要做的东西分析清楚,才好写代码。

采用MVC模式创建一个简单的javascript App的更多相关文章

  1. 【译】采用MVC模式创建一个简单的javascript App

    原文标题:Build A Simple Javascript App The MVC Way 作者:joshcrawmer4 翻译人:huansky 初次翻译,翻译的不好,还请见谅 JavaScrip ...

  2. 使用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(二)(代码篇)

    这篇是上一篇的延续: 用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(一) 源代码在github上可以下载,地址:https://github.com/guoxia ...

  3. BitAdminCore框架应用篇:(二)创建一个简单的增删改查模块

    NET Core应用框架之BitAdminCore框架应用篇系列 框架演示:http://bit.bitdao.cn 框架源码:https://github.com/chenyinxin/cookie ...

  4. 程序猿修仙之路--数据结构之你是否真的懂数组? c#socket TCP同步网络通信 用lambda表达式树替代反射 ASP.NET MVC如何做一个简单的非法登录拦截

    程序猿修仙之路--数据结构之你是否真的懂数组?   数据结构 但凡IT江湖侠士,算法与数据结构为必修之课.早有前辈已经明确指出:程序=算法+数据结构  .要想在之后的江湖历练中通关,数据结构必不可少. ...

  5. 如何创建一个简单的Visual Studio Code扩展

    注:本文提到的代码示例下载地址>How to create a simple extension for VS Code VS Code 是微软推出的一款轻量级的代码编辑器,免费,开源,支持多种 ...

  6. 《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型

    第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以 ...

  7. 如何创建一个简单的C++同步锁框架(译)

    翻译自codeproject上面的一篇文章,题目是:如何创建一个简单的c++同步锁框架 目录 介绍 背景 临界区 & 互斥 & 信号 临界区 互斥 信号 更多信息 建立锁框架的目的 B ...

  8. 无废话Android之listview入门,自定义的数据适配器、采用layoutInflater打气筒创建一个view对象、常用数据适配器ArrayAdapter、SimpleAdapter、使用ContentProvider(内容提供者)共享数据、短信的备份、插入一条记录到系统短信应用(3)

    1.listview入门,自定义的数据适配器 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/and ...

  9. Windows 8.1 应用再出发 (WinJS) - 创建一个简单项目

    前面几篇我们介绍了如何利用 C# + XAML 完成Windows Store App 功能的实现,接下来的几篇我们来看看如何利用 Html + WinJS 来完成这些功能. 本篇我们使用WinJS ...

随机推荐

  1. Social engineering tookit 钓鱼网站

    目录 Set 钓鱼攻击 网站克隆 Set Set(Social engineering tookit)是一款社会工程学工具,该工具用的最多的就是用来制作钓鱼网站. Kali中自带了该工具. 钓鱼攻击 ...

  2. POJ1722二维spfa+优先队列优化

    题意:      给你一个有向图,然后求从起点到终点的最短,但是还有一个限制,就是总花费不能超过k,也就是说每条边上有两个权值,一个是长度,一个是花费,求满足花费的最短长度. 思路:       一开 ...

  3. base标签的作用

    base标签可以自定义当前页面的相对路径,不受浏览器当前地址栏的影响,以base标签定义的地址为准 (只对相对路径起作用) <head> <meta charset="UT ...

  4. ConcurrentHashMap源码解读二

    接下来就讲解put里面的三个方法,分别是 1.数组初始化方法initTable() 2.线程协助扩容方法helpTransfer() 3.计数方法addCount() 首先是数组初始化,再将源码之前, ...

  5. C++ primer plus读书笔记——第8章 函数探幽

    第8章 函数探幽 1. 对于内联函数,编译器将使用相应的函数代码替换函数调用,程序无需跳到一个位置执行代码,再调回来.因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存. 2. 要使用内 ...

  6. 2020BUAA 软工-结对作业

    结对作业 项目 内容 北航2020软工 班级博客 作业要求 具体要求 1.在文章开头给出教学班级和可克隆的 GitHub 项目地址(例子如下).(1') 教学班级 005 项目地址 GitHub 2. ...

  7. mysql unique key

    create table b1(id int,name char unique)这样name字段就唯一了 或者create table b1(id int,name char,unique(id),u ...

  8. css背景|列表样式

    背景样式 背景区包含内容.padding 和 boder 不包含外边距 background-color 设置元素的背景颜色 background-image 把图像设置为背景,包含内边距和边框,不包 ...

  9. 020.Python生成器和生成器函数

    一 生成器 1.1 基本概念 元组推导式是是生成器(generator) 生成器定义 生成器可以实现自定义,迭代器是系统内置的,不能够更改 生成器的本质就是迭代器,只不过可以自定义. 生成器有两种定义 ...

  10. 基于Centos 7.4 搭建ELK整合SpringBoot日志收集

    基于Centos 7.4搭建es7.12.0+logstash-7.12.0+kibana-7.12.0(ELK)整合SpringBoot日志收集 注:Skywalking和logstash可共用一个 ...