JavaScript 中的模型-视图-控制器 (MVC)

JavaScript 是最强大的语言之一。它支持广泛的编程风格和技术,但这种灵活性伴随着危险。当没有遵循最佳实践或设计模式应用不正确时,JavaScript 项目相对容易变得混乱。

本文的目标是演示如何在开发简单的 JavaScript 组件时应用模型-视图-控制器模式。该组件是具有可编辑项目列表的 ListBox(“选择”HTML 标记)控件:用户应该能够选择和删除项目并将新项目添加到列表中。

我希望这篇文章本身对你来说可能是一个很好的阅读。但是如果您考虑运行示例并使用它们会更好。

模型-视图-控制器模式需要在这里进行一些描述。模式的名称由其参与者的名称组成: 模型 - 存储应用程序数据;View - 为客户渲染模型;和控制器 - 通过对客户端的操作做出反应来更新模型。Wikipedia 定义了部分模型-视图-控制器架构,如下所示:

  • 模型- 应用程序操作的信息的特定领域表示。模型是领域层的另一个名称。域逻辑为原始数据增加了意义(例如,计算今天是否是用户的生日,或者购物车项目的总数、税金和运费)。
  • 视图- 将模型呈现为适合交互的形式,通常是用户界面元素。MVC 经常出现在 Web 应用程序中,其中视图是 HTML 页面和为页面收集动态数据的代码。
  • 控制器- 处理和响应事件,通常是用户操作,并调用模型和视图的更改。

因此,让我们以反映该设计模式的各个部分的方式设计组件的主要类。

在实现 MVC 类之前,需要选择通知方法。在 JavaScript 中,没有关于如何实现发布-订阅模式的特殊语言结构或推荐接口。所有实现只使用函数引用。其中最常见的可能是EventEmitter实现,例如可以在 node.js 中找到。此实现的两个主要方法是用于添加事件处理程序的“on”和用于调用指定事件的事件处理程序的“emit”。通过从 EventEmitter 扩展通知或通过添加代理对 EventEmitter 内部实例的调用的方法,可以将通知添加到类中。

class EventEmitter {
  constructor() {
    this._events = {};
  }
  on(evt, listener) {
    (this._events[evt] || (this._events[evt] = [])).push(listener);
    return this;
  }
  emit(evt, arg) {
    (this._events[evt] || []).slice().forEach(lsn => lsn(arg));
  }
}

组件的数据只是一个项目列表,其中可以选择和删除一个特定项目。因此,组件的模型非常简单——它由一个数组和一个选定项索引组成;这是:

/**
 * The Model. Model stores items and notifies
 * observers about changes.
 */
class ListModel extends EventEmitter {
  constructor(items) {
    super();
    this._items = items || [];
    this._selectedIndex = -1;
  }

  getItems() {
    return this._items.slice();
  }

  addItem(item) {
    this._items.push(item);
    this.emit('itemAdded', item);
  }

  removeItemAt(index) {
    const item = this._items.splice(index, 1)[0];
    this.emit('itemRemoved', item);
    if (index === this._selectedIndex) {
      this.selectedIndex = -1;
    }
  }

  get selectedIndex () {
    return this._selectedIndex;
  }

  set selectedIndex(index) {
    const previousIndex = this._selectedIndex;
    this._selectedIndex = index;
    this.emit('selectedIndexChanged', previousIndex);
  }
}

在设计 View 之前,我们需要修复组件的 UI 结构。界面有很多替代方案,但就本文而言,最简单的一种更适合。让我们将项目保留在 Listbox 控件中,并在附近添加两个按钮:“加号”按钮用于添加项目,“减号”按钮用于删除选定项目。ListBox 将为我们提供选择项目和导航的低级机制。View 类与 Controller 类紧密绑定,后者“...处理来自用户界面的输入事件,通常通过注册的处理程序或回调”(来自wikipedia.org)。

以下是 View 和 Controller 类:

/**
 * The View. View presents the model and provides
 * the UI events. The controller is attached to these
 * events to handle the user interaction.
 */
class ListView extends EventEmitter {
  constructor(model, elements) {
    super();
    this._model = model;
    this._elements = elements;

    // attach model listeners
    model.on('itemAdded', () => this.rebuildList())
      .on('itemRemoved', () => this.rebuildList());

    // attach listeners to HTML controls
    elements.list.addEventListener('change',
      e => this.emit('listModified', e.target.selectedIndex));
    elements.addButton.addEventListener('click',
      () => this.emit('addButtonClicked'));
    elements.delButton.addEventListener('click',
      () => this.emit('delButtonClicked'));
  }

  show() {
    this.rebuildList();
  }

  rebuildList() {
    const list = this._elements.list;
    list.options.length = 0;
    this._model.getItems().forEach(
      item => list.options.add(new Option(item)));
    this._model.selectedIndex = -1;
  }
}

/**
 * The Controller. Controller responds to user actions and
 * invokes changes on the model.
 */
class ListController {
  constructor(model, view) {
    this._model = model;
    this._view = view;

    view.on('listModified', idx => this.updateSelected(idx));
    view.on('addButtonClicked', () => this.addItem());
    view.on('delButtonClicked', () => this.delItem());
  }

  addItem() {
    const item = window.prompt('Add item:', '');
    if (item) {
      this._model.addItem(item);
    }
  }

  delItem() {
    const index = this._model.selectedIndex;
    if (index !== -1) {
      this._model.removeItemAt(index);
    }
  }

  updateSelected(index) {
    this._model.selectedIndex = index;
  }
}

当然,模型、视图和控制器类应该被实例化:

Your favourite JavaScript technologies:<br>
<select id="list" size="10" style="width: 17em"></select><br>
<button id="plusBtn">  +  </button>
<button id="minusBtn">  -  </button>
window.addEventListener('load', () => {
  const model = new ListModel(['node.js', 'react']),
    view = new ListView(model, {
      'list' : document.getElementById('list'),
      'addButton' : document.getElementById('plusBtn'), 
      'delButton' : document.getElementById('minusBtn')
    }),
    controller = new ListController(model, view);

  view.show();
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值