使用MVC重构JavaScript客户端应用的实践建议

作者:木棉时间:2026-10-29 11:47:47浏览:106

随着Web应用重心向客户端转移,复杂的JavaScript代码变得难以维护。本文通过分析未规范代码的痛点,引入MVC(Model-View-Controller)架构思想,演示如何逐步重构代码。通过解耦DOM操作与Ajax请求、利用事件机制分离视图职责,以及引入数据模型,使代码结构更清晰、易于测试和扩展。

客户端应用复杂化趋势与MVC引入

你可能已经观察到,近期越来越多的Web应用呈现出复杂化趋势,开发重心正从服务端逐渐向客户端迁移。这一趋势是否合理尚存争议,支持与反对者的争论往往陷入无休止的辩论,难以判定绝对的对错。因此,本文不探讨哪种架构更优,而是尝试阐述如何运用大家熟知的面向对象编程理念,来解决客户端开发中的一些具体问题。

不规范代码示例与问题分析

不规范代码示例

为了兼顾应用的响应速度和用户体验,我们往往编写出日益复杂的代码,导致代码难以理解和维护。可以预见,如果在没有架构约束且缺乏规则的情况下构建客户端JavaScript应用,代码通常会呈现如下状态:


$(function(){
  $('#form').submit(function(e) {
    e.preventDefault();
 
    $.ajax({
      url: '/animals',
      type: 'POST',
      dataType: 'json',
      data: { text: $('#new-animal').find('textarea').val() },
      success: function(data) {
        $('#animals').append('<li>' + data.text + '</li>');
        $('#new-animal').find('textarea').val('');
      }
     });
   });
});

维护此类代码极具挑战性。仅这段简短的代码就与多个部分紧密耦合:它控制着多种事件(站点、用户、网络事件),处理用户交互,解析服务器响应并生成HTML。有人可能会反驳:“如果这不是单页应用(SPA),这最多只是过度使用jQuery的例子。”但这并非有力论点,因为易于维护且设计精良的代码至关重要。许多工具和框架致力于保持代码可用性,旨在简化测试、维护、重用和扩展过程。

理解MVC与MV*模式

MVC是什么?

在此背景下,我们可以从基于MVC的JavaScript框架中受益,但需注意,这些框架大多并未严格遵循MVC模式。它们往往将Model和View结合,或者介于两者之间,界限模糊。因此,大多数JavaScript框架实际上属于MV*模式。

改变开发方法或许能优化项目中的客户端组织和架构,使代码在长期内易于维护,即使进行重构也相对容易。理解其工作原理并回答以下问题是关键:

  • 应用中包含哪些类型的数据?- Model(模型)
  • 用户应该看到什么?- View(视图)
  • 谁负责与用户交互?- Controller(控制器)

使用MVC框架重构代码

使用MVC框架重构代码

使用MVC重构代码的好处?

  • 解除对DOM和Ajax的依赖。
  • 代码结构更清晰,且更易于测试。
  • 从 $(document).ready() 中移除冗余代码,仅保留使用Model创建链接的部分。

步骤一:创建视图并移动Ajax请求

让我们通过几个简单步骤来重构典型代码块: 步骤 1: 创建视图并移动Ajax请求

我们首先解除DOM与Ajax的依赖。使用原型构造函数模式创建 'Animals' 对象,并添加 'add' 方法。同时创建视图 'NewAnimalView',并添加 'addAnimal'、'appendAnimal' 和 'clearInput' 方法。

代码如下:


var Animals = function() {
};
 
Animals.prototype.add = function (options) {
   $.ajax({
     url: '/animals',
     type: 'POST',
     dataType: 'json',
     data: { text: options.text },
     success: options.success
   });
};
 
 var NewAnimalView = function (options) {
  this.animals = options.animals;
  var add = $.proxy(this.addAnimal, this);
  $('# form').submit(add);
 };
 
 NewAnimalView.prototype.addAnimal = function(e) {
   e.preventDefault();
   var self = this;
 
   this.animals.add({
     text: $('#new-animal textarea').val(),
     success: function(data) {
       self.appendAnimal (data.text);
       self.clearInput();     
     }
   });
 };
 
NewAnimalView.prototype.appendAnimal = function(text) {
  $('#animals ul').append('<li>' + data.text + '</li>');
};
NewAnimalView.prototype.clearInput = function() {
  $('#new-animal textarea').val('');
};
 
 $(document).ready(function() {
   var animals = new Animals();
   new NewAnimalView({ animals: animals });
 });

步骤二:使用事件解除依赖

步骤 2: 使用事件解除依赖。

此例中,利用MVC框架是关键。我们将使用事件机制,通过事件来结合和触发自定义事件。因此,我们创建新的“AnimalsView”和“NewAnimalView”,并赋予它们显示不同animals的职责。使用事件来区分职责非常简单。如果在方法和事件之间传递职责,如下所示:


var events = _.clone(Backbone.Events);
var Animals = function() {
};
 
Animals.prototype.add = function(text) {
   $.ajax({
     url: '/animals',
     type: 'POST',
     dataType: 'json',
     data: { text: text },
     success: function(data) {
      events.trigger('animal:add', data.text);
     }
   });
};
 
var NewAnimalView = function(options) {
  this.animals = options.animals;
  events.on('animal:add', this.clearAnimal, this);
  var add = $.proxy(this.addAnimal, this);
  $('# form').submit(add);
 };
 
NewAnimalView.prototype.addAnimal = function(e) {
   e.preventDefault();
   this.animals.add($('#new-animal textarea').val());
 };
 
NewAnimalView.prototype.clearInput = function() {
  $('#new-animal textarea').val('');
};
 
var AnimalsView = function() {
  events.on('animal:add', this.appendAnimal, this);
};
 
AnimalsView.prototype.appendAnimal = function(text) {
  $('#animals ul').append('<li>' + data.text + '</li>');
};
 
$(document).ready(function() {
   var animals = new Animals();
   new NewAnimalView({ animals: animals });
   new AnimalsView();
});

步骤三:将数据结构传递到核心框架

步骤 3: 将数据结构传递到核心框架

最后,也是最重要的一步,我们使用:models(模型)、views(视图)和 collections(集合)。


var Animal = Backbone.Model.extend({
  url: '/animals'
});
 
var Animals = Backbone.Collection.extend({
  model: Animal
});
 
var AnimalsView = Backbone.View.extend({
  initialize: function() {
    this.collection.on('add', this.appendAnimal, this);
  },
 
  appendAnimal: function(animal) {
    this.$('ul').append('<li>' + animal.escape('text') + '</li>');
  }
});
 
 
var NewAnimalView = Backbone.View.extend({
  events: {
    'submit form': 'addAnimal'
  },
 
  initialize: function() {
    this.collection.on('add', this.clearInput, this);
  },
 
  addAnimal: function(e) {
    e.preventDefault();
    this.collection.create({ text: this.$('textarea').val() });
  },
 
  clearInput: function() {
    this.$('textarea').val('');
  }
});
 
$(document).ready(function() {
  var animals = new Animals();
  new NewAnimalView({ el: $('#new-animal'), collection: animals });
  new AnimalsView({ el: $('#animals'), collection: animals });
});

重构总结与展望

总结

我们实现了什么?我们在高度抽象的层面上工作。代码的维护、重构和扩展变得更加容易。我们极大地优化了代码结构,这是否令人着迷?非常棒。但我可能需要泼一盆冷水:即使使用最好的框架,开发的代码仍然可能脆弱且难以维护。因此,认为使用较好的MV*框架就能解决所有代码问题是一种误解。请记住,在重构过程中,经历了第二步后,代码会变得好很多,而我们并未使用框架的主要组件。

虽然MV*框架是好的,但如果所有关注点都集中在‘如何’开发应用上,会让开发人员忽视‘什么’。对于领域复杂的项目,框架的一个补充将是领域驱动设计(Domain-Driven Design)方法,这将更关注以下方面:“what”,即将需求转化为实际产品的过程。但这将是我们要讨论的另一个主题。

作者最新文章