随着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”,即将需求转化为实际产品的过程。但这将是我们要讨论的另一个主题。