代码设计:组织 JavaScript

原文:The Design of Code: Organizing JavaScript
作者:Anthony Colangelo
译者:@涂鸦码龙

伟大的设计是一个产品应该关注和重视的点,这样才能造就一个可用的,好理解的,精美的用户界面。千万不要认为设计仅仅是设计师的事。

代码里面有好多种设计,我不是指代码构建了用户界面——我是指代码的设计。

精心设计的代码更易于维护,优化和扩展,能使开发者更高效。这意味着更多的注意力和精力可以花在构建伟大的事情上,每个人都很愉快——用户,开发者和利益相关者。

有3个高级的,跟语言无关的点,对代码设计十分重要。

  1. 系统架构——代码库的基础设计。控制各种组件的规则,例如模块(models),视图(views)和控制器(controllers),以及之间的相互作用。
  2. 可维护性——如何更好地改进和扩展代码?
  3. 复用性——应用组件如何复用?每个组件的实例如何简便地个性定制?

比较宽松的语言,特别是 JavaScript ,需要一些规矩才能写好——代码设计。JavaScript 环境非常宽松,随处扔些代码片段,就可能起作用。早点确立系统架构(然后遵守它!)对你的代码库提供制约,确保自始至终的一致性。

我比较喜欢的一个方法由久经考验的软件设计模式组成——模块模式,可扩展的结构使它成为可靠的系统架构,和可维护的代码库。我喜欢在一个 jQuery 插件里面构建模块,有漂亮的复用性,提供强健的选项设置,暴露一些精心设计的 API。

下面,我将逐步讲解如何精心设计你的代码,把它变成结构良好的组件,从而在项目中复用。

模块模式

已有许多设计模式,同样有许多相关资源。Addy Osmani 写了本关于 JavaScript 设计模式的书《令人惊奇的书(免费!)》,我极力推荐给各个水平的开发者。

模块模式 是一个简单的结构基础,它可以让你的代码保持干净和条例清晰。一个“模块”就是个标准的包含方法和属性的对象字面量,简单是这个模式的最大亮点:甚至一些不熟悉传统的软件设计模式的人,一看就能立刻明白代码是如何工作的。

用此模式的应用,每个组件有它独立的模块。例如,创建自动完成功能,你要写个模块用于文本区域,一个模块用于结果列表。两个模块相互工作,但是文本区域代码不会触及结果列表代码,反之亦然。

模块解耦是模块模式非常适于构建可靠的系统架构的原因。应用间的关系是明确定义的;任何关系到文本区域的事情被文本区域模块管理,并不是散落在代码库中——代码整洁。

模块化组织的另一个好处是固有的可维护性。模块可以独立地改进和优化,不会影响应用的任何其它部分。

我的 jPanelMenu 用到了模块模式,我创建此 jQuery 插件用于关闭画布(off-canvas)的菜单系统。我将用这个例子演示创建模块的过程。

创建一个模块

首先,我定义三个方法和一个属性,用于管理菜单系统的交互。

1
2
3
4
5
6
7
8
9
10
11
12
var jpm = {
animated: true,
openMenu: function( ) {
this.setMenuStyle( );
},
closeMenu: function( ) {
this.setMenuStyle( );
},
setMenuStyle: function( ) { … }
};

这个想法把代码拆分到最小能复用的程度。我还可以写成一个 toggleMenu( ) 方法,但是创建单独的 openMenu( )closeMenu( ) 更利于模块间的复用。

注意到模块方法和属性的调用(例如 setMenuStyle( ) 调用),用到了 this 关键字——模块通过它访问自己的成员。

这是模块的基础结构。你可以继续添加需要的方法和属性,一点也不复杂。在基础结构就位后,复用层——选项和暴露 API ——可以在此基础上创建。

jQuery 插件

精心设计的第三方代码最关键的是:复用性。用原生的 JavaScript 同样可以实现重用组件,我更喜欢用 jQuery 插件做更复杂的事情,有以下几个原因。

最重要的是,它是一个不显眼的沟通形式。如果你用 jQuery 创建一个组件,具体实现非常明确。构建一个组件作为 jQuery 插件等于说 jQuery 是必须的。

此外,实现代码将与其余的基于 jQuery 的项目代码保持一致,意思是开发者不需要太多研究就可以学会与插件的交互。

在你开始创建 jQuery 插件前,确保插件没有跟其他用 $ 符号的 JavaScript 库冲突。非常简单——像这样包裹你的插件代码:

1
2
3
(function($) {
// jQuery plugin code here
})(jQuery);

下一步,我们设置插件,并把先前的模块代码放到里面。一个插件仅仅是定义在 jQuery ($)对象上的一个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function($) {
$.jPanelMenu = function( ) {
var jpm = {
animated: true,
openMenu: function( ) {
this.setMenuStyle( );
},
closeMenu: function( ) {
this.setMenuStyle( );
},
setMenuStyle: function( ) { … }
};
};
})(jQuery);

使用插件时调用你创建的函数即可。

1
var jpm = $.jPanelMenu( );

选项设置

选项设置是任何可复用的插件所必须的,因为它允许自定义。每一个项目都带来了一系列的设计风格、交互类型和内容结构。自定义选项帮助插件去适应项目的约束。

最佳实践是为选项提供合适的默认值。最容易的方式是用 jQuery 的 $.extend( ) 方法,它接收(至少)两个参数。

$.extend( ) 的第一个参数,定义一个对象包含所有可用的选项,以及默认值。第二个参数传入选项。它将合并两个对象,传入选项时会覆盖默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(function($) {
$.jPanelMenu = function(options) {
var jpm = {
options: $.extend({
'animated': true,
'duration': 500,
'direction': 'left'
}, options),
openMenu: function( ) {
this.setMenuStyle( );
},
closeMenu: function( ) {
this.setMenuStyle( );
},
setMenuStyle: function( ) { … }
};
};
})(jQuery);

除了提供默认值,选项几乎可以自我说明——人们看代码时立刻看到了所有可用的选项。

暴露尽可能多的选项是可行的,将有助于在未来实现定制和灵活性。

API

选项是自定义插件的好方法。另一方面, API 通过暴露属性和方法,启用扩展插件的功能。

尽可能通过 API 暴露,外部不应该直接访问到所有内部的方法和属性。理想情况下,你应该仅公开使用的元素。

我们的例子,暴露的 API 应该包含打开和关闭菜单,仅此而已。内部的 setMenuStyle( )方法在菜单打开和关闭的时候运行,但是全局不需要访问它。

为了公开 API,在插件代码最后,返回一个带有期望的方法和属性的对象。甚至可以把返回的属性和方法放到模块代码中——这是模块模式漂亮的组织形式的真正亮点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(function($) {
$.jPanelMenu = function(options) {
var jpm = {
options: $.extend({
'animated': true,
'duration': 500,
'direction': 'left'
}, options),
openMenu: function( ) {
this.setMenuStyle( );
},
closeMenu: function( ) {
this.setMenuStyle( );
},
setMenuStyle: function( ) { … }
};
return {
open: jpm.openMenu,
close: jpm.closeMenu,
someComplexMethod: function( ) { … }
};
};
})(jQuery);

通过对象的返回及插件的初始化,API 方法和属性将变得可用。

1
2
3
4
5
var jpm = $.jPanelMenu({
duration: 1000,
});
jpm.open( );

打磨开发者接口

通过几个简单的步骤和指导,我们已经构建了可复用,可扩展的插件。实验一下这一架构是否适合你,你的团队,以及你的工作流程。