vanilla_Vanilla JavaScript中的MVC设计模式

vanilla

在好莱坞标志前拥有笔记本电脑的开发人员-MVC设计模式

设计模式通常被合并到流行的框架中。 例如, 模型-视图-控制器(MVC)设计模式是一种普遍存在的模式。 在JavaScript中,很难将框架与设计模式脱钩。 通常,特定的框架会带有自己对此设计模式的解释。 框架附带意见,每个框架都迫使您以某种方式进行思考。

现代框架规定了MVC模式的具体实现是什么样的。 当所有解释都不同时,这会造成混乱,从而增加噪音和混乱。 当任何代码库采用多个框架时,都会造成令人沮丧的混乱。 我心中的问题是,有没有更好的方法?

MVC模式适用于客户端框架,但是现代框架正在发生变化。 今天的现代是随着时间的流逝而消逝的。 在这种情况下,我想探索替代方法,并了解一些纪律性要求。

MVC模式本身可以追溯到几十年前。 这使之成为投资编程技能的良好设计模式。MVC模式是可以独立存在的设计模式。 问题是,这可以带我们走多远?

等等,这是另一个框架吗?

首先,我想消除这个普遍的神话:设计模式不是框架。 设计模式是一种解决代码问题的规范方法。 有一定水平的技能,必须由程序员负责。 设计模式将关注点分开并提倡简洁的代码。

框架是不同的,因为它不必遵守任何设计模式。 从模式中分辨框架的一种方法是寻找好莱坞原则。 好莱坞的原则是:“不要给我们打电话,我们会给您打电话。” 任何时候只要有依赖项指示何时使用它,它就是一个框架。 框架与好莱坞很像,因为您没有就该做什么或如何做的发言权。 实际上,开发人员就像演员,因为他们在被要求采取行动时会遵循脚本。

有充分的理由避免使用客户端框架:

  • 框架增加了解决方案的复杂性和风险
  • 您遇到依赖项锁定,从而导致代码无法维护
  • 随着新的流行框架的出现,很难重写现有的遗留代码

MVC模式

MVC设计模式是从1970年代的Xerox Smalltalk研究项目发展到80年代的。 这种模式经受了前端图形用户界面的时间考验。 该模式来自桌面应用程序,但事实证明也对Web应用程序有效。

关键在于,MVC设计模式是关注点的清晰划分。 目的是使解决方案易于理解和吸引人。 任何希望进行特定更改的程序员都可以轻松找到合适的位置。

企鹅演示

企鹅! 可爱而可爱,地球上一些最毛茸茸的生物。 如此可爱,实际上,有17种不同的企鹅并不都生活在南极洲。

是时候演示企鹅了! 我将在一个页面上显示一个甲板,其中显示了几种物种。 为此,我想使用MVC设计模式和一些技巧。 我将使用极限编程方法来解决单元测试中的问题,而且不要胡扯。 最后,您应该能够浏览一些企鹅,每个企鹅都有自己的数据和个人资料图片。

在本示例的最后,您应该已经学到足够的知识,可以在纯JavaScript中使用MVC设计模式。 模式本身是超级可测试的,因此需要良好的单元测试。

由于跨浏览器兼容性的原因,我将坚持使用ES5进行此演示。 在这种常年性的设计模式中使用经过验证的语言功能很有意义。

你准备好了吗? 让我们找出答案。

骨架

该演示将包括三个主要部分:控制器,视图和模型。 每个问题都有其自己的关注点和需要解决的问题。

外观如下所示:

企鹅视觉演示

PenguinController处理事件,并且是视图和模型之间的中介。 它可以计算出用户执行某项操作(例如,单击按钮或按下键)时会发生什么。 客户端特定的逻辑可以进入控制器。 在发生很多事情的较大系统中,您可以将其分解为模块。 控制器是事件的入口点,并且是视图和数据之间的唯一中介。

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

PenguinView关心DOM。 DOM是用于进行HTML操作的浏览器API。 在MVC中,除了视图之外,没有其他部分关心更改 DOM。 该视图可以附加用户事件,但将事件处理问题留给控制器。 视图的主要指令是更改用户在屏幕上看到的状态。 对于此演示,视图将使用纯JavaScript进行DOM操作。

PenguinModel关心数据。 在客户端JavaScript中,这表示Ajax。 MVC模式的一个优点是您现在可以在服务器端进行Ajax调用了。 这对不熟悉该解决方案的其他程序员很有吸引力。 此设计模式中的模型仅关心JSON或来自服务器的对象。

一种反模式是违反这种固有的关注点分离。 例如,该模型必须不关心HTML。 该视图一定不在乎Ajax。 控制器必须充当中介者,而不必担心实现细节。

我发现这种模式是开发人员的初衷是善意的,但担心泄漏。 诱人的是将所有内容都变成一个Web组件并以糊糊告终。 重点放在功能和用户面临的问题上。 但是,功能问题与功能问题不同。

在编程中,我喜欢对功能问题进行明确的分离。 每个单独的编程问题都有解决问题的一致方法。 这使您在阅读代码时更加容易理解。 这个想法是编写引人入胜的代码,以便其他人也可以做出积极的贡献。

如果没有可以看到和触摸的真实示例,那么演示就不算什么。 因此,事不宜迟,下面是展示企鹅演示的CodePen:

请参阅CodePen上的SitePoint@SitePoint )提供的Pen Pen 企鹅演示

聊够了,花些时间编写一些代码。

控制器

视图和模型是控制器使用的两个组件。 控制器在其构造函数中具有完成该工作所需的所有组件:

var PenguinController = function PenguinController(penguinView, penguinModel) {
  this.penguinView = penguinView;
  this.penguinModel = penguinModel;
};

构造函数使用控件的反转并以这种方式注入模块。 通过此模式,您可以注入满足高级合同的任何组件。 可以将其视为从实现细节中抽象代码的好方法。 这种模式使您能够使用纯JavaScript编写干净的代码。

然后,通过以下方式连接并处理用户事件:

PenguinController.prototype.initialize = function initialize() {
  this.penguinView.onClickGetPenguin = this.onClickGetPenguin.bind(this);
};

PenguinController.prototype.onClickGetPenguin = function onClickGetPenguin(e) {
  var target = e.currentTarget;
  var index = parseInt(target.dataset.penguinIndex, 10);

  this.penguinModel.getPenguin(index, this.showPenguin.bind(this));
};

请注意,此事件使用当前目标来获取存储在DOM中的状态。 在这种情况下,DOM会告诉您所有需要了解的有关其当前状态的信息。 DOM的当前状态是用户在浏览器上看到的内容。 您可以将状态数据存储在DOM本身中,只要控制器不更改状态即可。

触发事件后,控制器将获取数据并说明接下来会发生什么。 this.showPenguin()回调很有趣:

PenguinController.prototype.showPenguin = function showPenguin(penguinModelData) {
  var penguinViewModel = {
    name: penguinModelData.name,
    imageUrl: penguinModelData.imageUrl,
    size: penguinModelData.size,
    favoriteFood: penguinModelData.favoriteFood
  };

  penguinViewModel.previousIndex = penguinModelData.index - 1;
  penguinViewModel.nextIndex = penguinModelData.index + 1;

  if (penguinModelData.index === 0) {
    penguinViewModel.previousIndex = penguinModelData.count - 1;
  }

  if (penguinModelData.index === penguinModelData.count - 1) {
    penguinViewModel.nextIndex = 0;
  }

  this.penguinView.render(penguinViewModel);
};

控制器为每个企鹅计算索引,并告诉视图进行渲染。 它从模型中获取数据,并将其转换为视图可以理解和关心的对象。

这是显示企鹅时幸福道路的单元测试:

var PenguinViewMock = function PenguinViewMock() {
  this.calledRenderWith = null;
};

PenguinViewMock.prototype.render = function render(penguinViewModel) {
  this.calledRenderWith = penguinViewModel;
};

// Arrange
var penguinViewMock = new PenguinViewMock();

var controller = new PenguinController(penguinViewMock, null);

var penguinModelData = {
  name: 'Chinstrap',
  imageUrl: 'http://chinstrapl.jpg',
  size: '5.0kg (m), 4.8kg (f)',
  favoriteFood: 'krill',
  index: 2,
  count: 5
};

// Act
controller.showPenguin(penguinModelData);

// Assert
assert.strictEqual(penguinViewMock.calledRenderWith.name, 'Chinstrap');
assert.strictEqual(penguinViewMock.calledRenderWith.imageUrl, 'http://chinstrapl.jpg');
assert.strictEqual(penguinViewMock.calledRenderWith.size, '5.0kg (m), 4.8kg (f)');
assert.strictEqual(penguinViewMock.calledRenderWith.favoriteFood, 'krill');
assert.strictEqual(penguinViewMock.calledRenderWith.previousIndex, 1);
assert.strictEqual(penguinViewMock.calledRenderWith.nextIndex, 3);

PenguinViewMock具有与实际实现相同的约定。 这使得编写单元测试和进行断言成为可能。 该assert来自Node断言 ,在Chai断言中也可用。 这使您能够编写可以在Node和浏览器上运行的测试。

注意,控制器并不关心实现细节。 它使用视图提供的合同,例如this.render() 。 这是编写干净代码所必需的准则。 控制器可以信任每个组件执行其将要执行的操作。 这增加了透明度,使代码可读。

风景

该视图仅关心DOM元素和连接事件,例如:

var PenguinView = function PenguinView(element) {
  this.element = element;

  this.onClickGetPenguin = null;
};

当它更改用户看到的状态时,实现如下所示:

PenguinView.prototype.render = function render(viewModel) {
  this.element.innerHTML = '<h3>' + viewModel.name + '</h3>' +
    '<img class="penguin-image" src="' + viewModel.imageUrl +
      '" alt="' + viewModel.name + '" />' +
    '<p><b>Size:</b> ' + viewModel.size + '</p>' +
    '<p><b>Favorite food:</b> ' + viewModel.favoriteFood + '</p>' +
    '<a id="previousPenguin" class="previous button" href="javascript:void(0);"' +
      ' data-penguin-index="' + viewModel.previousIndex + '">Previous</a> ' +
    '<a id="nextPenguin" class="next button" href="javascript:void(0);"' +
      ' data-penguin-index="' + viewModel.nextIndex + '">Next</a>';

  this.previousIndex = viewModel.previousIndex;
  this.nextIndex = viewModel.nextIndex;

  // Wire up click events, and let the controller handle events
  var previousPenguin = this.element.querySelector('#previousPenguin');
  previousPenguin.addEventListener('click', this.onClickGetPenguin);

  var nextPenguin = this.element.querySelector('#nextPenguin');
  nextPenguin.addEventListener('click', this.onClickGetPenguin);
  nextPenguin.focus();
};

注意,它的主要关注点是将视图模型数据转换为HTML并更改状态。 第二个是连接单击事件,并让控制器充当入口点。 状态更改后,事件处理程序将附加到DOM。 此技术可以一目了然地处理事件管理。

为了测试这一点,我们可以验证元素是否已更新并更改状态:

var ElementMock = function ElementMock() {
  this.innerHTML = null;
};

// Stub functions, so we can pass the test
ElementMock.prototype.querySelector = function querySelector() { };
ElementMock.prototype.addEventListener = function addEventListener() { };
ElementMock.prototype.focus = function focus() { };

// Arrange
var elementMock = new ElementMock();

var view = new PenguinView(elementMock);

var viewModel = {
  name: 'Chinstrap',
  imageUrl: 'http://chinstrap1.jpg',
  size: '5.0kg (m), 4.8kg (f)',
  favoriteFood: 'krill',
  previousIndex: 1,
  nextIndex: 2
};

// Act
view.render(viewModel);

// Assert
assert(elementMock.innerHTML.indexOf(viewModel.name) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.imageUrl) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.size) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.favoriteFood) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.previousIndex) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.nextIndex) > 0);

这解决了所有大问题,状态更改和接线事件。 但是,数据从哪里来?

该模型

在MVC中,所有模型关心的都是Ajax。 例如:

var PenguinModel = function PenguinModel(XMLHttpRequest) {
  this.XMLHttpRequest = XMLHttpRequest;
};

注意,模块XMLHttpRequest被注入到构造函数中。 这是一种让其他程序员知道该模型需要哪些组件的方法。 如果模型需要的不仅仅是简单的Ajax,则可以使用更多模块来发出信号。 另外,通过单元测试,我可以注入与原始模块具有完全相同协定的模拟。

根据索引获取企鹅的时间:

PenguinModel.prototype.getPenguin = function getPenguin(index, fn) {
  var oReq = new this.XMLHttpRequest();

  oReq.onload = function onLoad(e) {
    var ajaxResponse = JSON.parse(e.currentTarget.responseText);
    // The index must be an integer type, else this fails
    var penguin = ajaxResponse[index];

    penguin.index = index;
    penguin.count = ajaxResponse.length;

    fn(penguin);
  };

  oReq.open('GET', 'https://codepen.io/beautifulcoder/pen/vmOOLr.js', true);
  oReq.send();
};

这指向一个端点并从服务器获取数据。 我们可以通过使用单元测试模拟数据来测试它:

var LIST_OF_PENGUINS = '[{"name":"Emperor","imageUrl":"http://imageUrl",' +
  '"size":"36.7kg (m), 28.4kg (f)","favoriteFood":"fish and squid"}]';

var XMLHttpRequestMock = function XMLHttpRequestMock() {
  // The system under test must set this, else the test fails
  this.onload = null;
};

XMLHttpRequestMock.prototype.open = function open(method, url, async) {
  // Internal checks, system under test must have a method and url endpoint
  assert(method);
  assert(url);
  // If Ajax is not async, you’re doing it wrong :-)
  assert.strictEqual(async, true);
};

XMLHttpRequestMock.prototype.send = function send() {
  // Callback on this object simulates an Ajax request
  this.onload({ currentTarget: { responseText: LIST_OF_PENGUINS } });
};

// Arrange
var penguinModel = new PenguinModel(XMLHttpRequestMock);

// Act
penguinModel.getPenguin(0, function onPenguinData(penguinData) {

  // Assert
  assert.strictEqual(penguinData.name, 'Emperor');
  assert(penguinData.imageUrl);
  assert.strictEqual(penguinData.size, '36.7kg (m), 28.4kg (f)');
  assert.strictEqual(penguinData.favoriteFood, 'fish and squid');
  assert.strictEqual(penguinData.index, 0);
  assert.strictEqual(penguinData.count, 1);
});

如您所见,该模型仅关心原始数据。 这意味着使用Ajax和JavaScript对象。 如果您对Vanilla JavaScript中的Ajax不清楚,则可以参阅这篇文章,了解更多信息。

单元测试

无论采取任何纪律,重要的是要做必要的工作以获得保证。 MVC设计模式并不决定如何解决问题。 设计模式为您提供了广泛的界限,使您能够编写简洁的代码。 这使您免于依赖的压迫。

对我来说,这意味着每个用例都有完整的单元测试套件。 这些测试提供了有关如何使用代码的指导。 这使它变得开放并吸引任何希望进行特定更改的程序员。

随意查看整个单元测试集 。 我认为它将帮助您了解这种设计模式。 每个测试都针对特定的用例; 认为它是关注点。 单元测试可帮助您单独考虑每个编码问题并解决这一问题。 每个单元测试中,MVC中功能关注点的分离变得栩栩如生。

展望未来

企鹅演示仅提供了一个可行的概念来展示MVC的实用性。 但是,有很多改进可以迭代:

  • 添加一个包含所有企鹅列表的屏幕
  • 添加键盘事件,以便您可以翻阅企鹅,也可以添加滑动
  • SVG图表以可视化数据,选择任何数据点,例如企鹅大小

当然,我的读者请自行决定是否进一步推广此演示。 这些只是一些想法,因此您可以展示此设计模式的强大功能。

结论

我希望您能看到MVC设计模式和一些纪律可以带您去哪里。 良好的设计模式在推广简洁代码的同时不会受到影响。 它可以让您专心解决问题,同时仅解决当前的问题。 它使您成为一个更好,更有效的程序员。

在编程中,其思想是在解决问题的同时保持手头的问题。 编程技术一次充实了一个单一的问题。 在MVC中,这意味着一次只关注一个功能。

作为开发人员,很容易相信您是有逻辑的,并且不会情绪激动。 事实是,一次您被太多问题困扰,感到沮丧。 这是我们所有人都必须应对的正常人类React。 事实是,挫败感会对代码质量产生负面影响。 当这种感觉抓住了您并支配了您的工作时,它就不再与逻辑有关。 随着解决方案承担更多的风险和复杂的依赖关系,这可能使人丧气。

我喜欢的只是关注一个问题。 一次解决一个问题并获得积极的反馈。 这样,您就可以呆在区域中,高效而无聊。

本文由Vildan Softic同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!

翻译自: https://www.sitepoint.com/mvc-design-pattern-javascript/

vanilla

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值