(本文发表在InfoQ:http://www.infoq.com/cn/articles/using-decj-simplify-web-front-end-development-part1)
引言
Web前端开发中,开发人员经常需要处理一些常规问题,如:
- 在页面中引用多个相互存在依赖关系的Javascript文件
- 在页面中引用CSS文件
- 浏览器事件绑定
- 表单的数据填充、数据打包提交、数据校验和格式化
- 页面初始化逻辑
采用传统的命令式编程范式来处理这些问题时,开发人员不得不反复地通过编写代码调用相关API来完成这些常规任务。事实上,开发人员的主要精力应该 集中在业务逻辑实现上,而非在这些常规任务上过多消耗时间。声明式编程范式可以帮助开发人员以最小的工作量去快速搞定这些常规任务,从而能够将更多的精力 放在业务逻辑的实现上。
decj是一款以声明式编程范式为基础的Javascript开源框架。本文将介绍如何使用decj框架以近乎零编码的高效率方式去搞定这些常规任务。本期将介绍decj的声明式Javascript文件动态按需加载和声明式跨浏览器事件绑定。
decj框架简介
decj的优势及主要特性
decj使得开发人员能够进行模块化的声明式编程,其目标在于简化Web应用开发中常规问题的处理,使得开发人员能够将更多的精力放在业务逻辑处理上。简单来说,decj的优势在于:
- 声明式编程:使得开发人员能够集中精力在写必须由其编写的代码,提升开发效率。
- decj所解决的问题是几乎每个Web应用中都要面对的普遍的问题:如事件绑定、表单数据校验、格式化、表单提交等。
- 代码即文档(Code is document):采用decj开发的应用,其代码某种程度上就是文档。
decj的优势也就是声明式编程的优势。通过声明式编程,decj使得开发者面对日常工作中经常要处理的问题时能够集中精力在“真正”需要其处理的问题上。
比如,但某个页面上的一个按钮被单击时,一段业务处理逻辑需要被执行。显然,这段被调用的业务逻辑才是开发者真正要集中精力处理的问题,因为业务逻 辑是怎么样的只有人才能知道,而任何框架/库是无法得知并为开发人员代劳的。相反,采用命令式编程的框架/库,在处理此类问题时,开发人员往往得首先分心 去处理一些非业务逻辑的问题,比如,如何让这个按钮响应单击事件。比如,若使用jQuery来实现,开发人员需要在代码中的恰当位置/时机(如
window的onload事件被触发后)调用jQuery的bind方法,才能使按钮被单击后执行一段业务逻辑。如清单1代码所示:
清单 1. 命令式编程:使用jQuery处理事件绑定
$(document).ready(function(){//加载页面完毕后执行该函数 $('#aButton').bind('click',function(){//在此处编写或者调用业务逻辑实现代码});} );
而采用decj,开发人员无须关心事件处理API以及在何处、何时机调用这些API。可以更加关注业务逻辑。如清单2代码所示:
清单 2. 声明式编程:使用decj处理事件绑定
events:{ "click@aButton":function(){//按钮aButton被单击时执行该函数 //直接在此处编写或者调用业务逻辑实现代码 } }
可以看出,清单2的代码中并没有关于事件API的调用,开发人员因此可以不必关注这些API以及何时在何地方调用它们。开发人员可以重点关注如何响 应相关事件,以实现业务逻辑。另外,decj的应用代码可以充当文档。例如,如清单2的代码所示,对于页面上的某个元素的某个事件,是采用那个事件处理器 响应的这个信息一目了然,这有助于问题定位,因为定位问题的人可以快速确认他需要的信息。
decj支持以下几个主要特性:
- 声明式Javascript文件按需动态加载
- 声明式跨浏览器的事件绑定
- 声明式CSS文件按需动态加载
- 声明式HTML表单增强:表单内容自动填充、表单数据自动提交、表单重置增强、表单数据校验、数据格式化
- 声明式页面/模块初始化
- 声明式国际化(I18N)支持:支持多语言和按需加载资源文件
- HTML代码/CSS文件/Javascript文件/资源文件并行加载
下面我们详细介绍decj框架的两大基础---声明式编程和Javascript模块化编程。若读者已经熟悉这两基础可以跳过这两部分内容。然后, 我们将 从Web前端开发中的日常任务入手,探讨这些日常任务的常规实现方法的弊端以及如何利用decj的声明式编程去克服或绕过这些弊端。
声明式编程
多数通用编程语言(如Java语言)都采用命令式编程范式(Imperative)。这种方式下的编程,我们不仅要告诉机器它要做什么,还要告诉它 如何去完成。而声明式(Declarative)编程是一种只需要告诉机器要完成什么,而无需说明如何去完成的一种编程范式。声明式编程往往能够减少开发 人员的工作量,使代码更加简洁和富有表现力。
声明式编程的一个常见例子是数据库查询语言SQL(Structured Query Language)。如下一个SQL查询语句,它仅仅说明了要查询什么样的数据,而无需说明如何去查询这些数据:
清单 3. 声明式编程的例子—SQL语句
SELECT license FROM frameworks WHERE name=’decj’;--查询名为decj的框架的许可证信息
Javascript语言所支持的JSON(JavaScript Object Notation)语法非常适合于作为Javascript声明式编程的语法基础。比如,假设某个Javascript UI(User Interface)库提供了一个名为createDialog的函数用于创建基于HTML的网页对话框。该函数接受一个参数,用于配置所要创建的对话框 的一些属性和行为。那么,在调用createDialog函数就可以使用JSON语法来声明所要创建的对话框的属性。如清单4所示:
清单 4. JSON作为Javascript声明式编程的基础
createDialog({//创建一个标题为“成功”,宽100像素,高200像素的模态对话框 width:"100px", height:"200px", modal:true, title:"成功"});
Javascript模块化编程
声明式编程是decj框架的核心,而模块化编程是其基础。decj框架并不“再造车轮(Re-invent the wheel)”,它默认采用遵循AMD(Asynchronous Module Definition)规范的Javascript库RequireJS来实现模块化编程。
AMD规范定义了一个名为define的函数,通过该函数开发人员可以定义一个Javascript模块。在AMD规范中,一个Javascript模块可以是任何的Javascript对象,如函数、数组和普通Javascript对象。define函数的签名如下:
define(id?,dependencies?,factory);
该函数的各个参数含义如下:
id:可选字符串,表示所要定义的模块的唯一标识(ID)。
dependencies:可选数组,表示当前模块所依赖的其它各个模块的唯一标识。
factory:模块工厂。通常是一个匿名函数,负责返回所要定义的Javascript模块。该函数接受若干个参数,每个参数都与dependencies参数中指定的ID对应的Javascript模块对象一一对应。
如下代码定义了一个Javascript模块,该模块是一个普通的Javascript对象,如清单5所示:
清单 5. 基于AMD规范的Javascript模块化编程
define(['jquery'],function(jquery){ //该模块工厂依赖于jQuery库,故声明ID为jquery的模块为其依赖模块 //返回一个模块对象。该对象包含一个名为fieldValue的方法 return { fieldValue:function(fieldName){ //返回当前页面中名为fieldName的表单控件的值 return jquery("[name='+fieldName+']").val(); } //... }; });
AMD规范定义了另一名为require的函数用于加载指定的Javascript模块。不过,由于decj框架的声明式编程的特性,应用开发者一般无需使用该函数了。因此,本文不详细介绍该函数了。
开始使用decj
首先,从github下载decj框架,并将其部署到你的Web服务器上。
然后,编写Web应用的主页。并在主页中添加一个script标签,使该标签的src属性和data-main属性分别引用RequireJS和decj框架的Javascript文件。如清单6所示:
清单 6. 开始使用decj框架
<html>
<head>
<title>decj startup</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
function decjApp(){
return {
initialModule:'../module/initModule' //定义初始模块的ID
};
}
</script>
<script data-main="../js/lib/decj.js" src="../js/lib/requirejs.js"></script>
</head>
<body>
开始使用decj!
</body>
</html>
模块化编程是decj的基础。一个Web应用可以包含多个模块,因此基于decj的Web应用需要一段应用启动代码。这段代码会定义Web应用的初始模块(即第一个会被加载的模块)的ID。
应用启动代码是一个名为decjApp的函数,该函数返回一个普通的Javascript对象。该对象的initialModule属性用于指定当 前应用第一个要加载的模块(初始模块),如清单6所示。decjApp函数返回值的其它属性还可以用来定义decj的其它属性和行为。
最后,编写decjApp函数指定的初始模块对应的代码,这样一个基本的decj Web应用就可以使用了。清单7显示了一个示例模块代码。
清单 7. 一个decj示例模块
define(['jquery','decj'],function(jQuery,decj){ alert('decj comes into play!'); return {//返回模块对象 }; });
声明式Javascript文件动态按需加载
前文讲到,模块化编程是decj的基础。采用基于AMD规范的Javascript模块化编程的好处不仅仅是降低各个模块的耦合度,一定程度上也可 以提高应用的性能。因为各个Javascript模块及其所依赖的其它模块只有当其代码确实要被调用的时候才会由模块加载器去动态加载。
decj默认采用RequireJS作为其模块加载器,因此基于decj框架开发的应用中的各个Javascript文件在运行的时候是被动态按需 加载的,而不是页面一加载时就把所有可能用的Javascript都一起加载。事实上,开发人员也可以选择使用其它符合AMD规范的模块加载器。
声明式跨浏览器事件绑定
客户端编程中,事件绑定是一个几乎每天都要处理的一个问题。比如,要使页面上一个ID为chkShowLog的checkbox响应单击事件。当该 checkbox被单击时,ID为log的元素会在被显示和隐藏之间来回切换。采用传统的编程范式来实现这样简单的一个功能,即便在采用jQuery这样 能够使我们编写简练代码的Javascript库的情况下,开发人员也不得不编写代码来调用浏览器事件处理的相关API,如清单8所示:
清单 8. 使用jQuery实现事件绑定
$(document).ready(function(){//在页面加载完毕后执行调用该函数 $('#chkShowLog').bind('click',function(ele){//ID为chkShowLog的被单击后执行该函数 $('#log').toggle();//显示或隐藏ID为log的元素 }); });
如果不采用任何Javascript库或框架,直接使用Javascript来实现清单8代码的功能,并且还要兼容不同浏览器的话,那么需要编写的代码就更加多和繁琐了。
decj支持声明式的事件绑定。应用代码只需要在模块定义中声明哪个元素(事件目标元素)的哪个事件使用哪个监听器来处理,而无需调用任何与事件绑定有关的浏览器或者框架的API。例如,清单8中的代码使用decj可以改写为清单9所示的代码:
清单 9. 使用decj的声明式事件绑定
define(["decj"],function(decj){ return { //… events:{ "click@#chkShowLog":function(){ //使该函数响应ID为chkShowLog的元素的onclick事件 $("#log").toggle(); } } //… }; });
上面的的代码乍一看似乎比清单8中的代码要长。但事实上,清单9中的代码,除了下面清单10中的代码片段,其余的都是定义一个模块所必须的代码,而 不属于事件绑定本身。并且,清单9中的代码没有任何API调用,开发人员只需要通过代码告诉框架本身无法知道的信息(即我们要采用哪个函数响应哪个元素的 哪个事件)。
清单 10. 使用decj的声明式事件绑定
events:{ "click@#chkShowLog":function(){//使该函数响应ID为chkShowLog的元素的onclick事件 $("#log").toggle(); } }
decj的声明式事件绑定是跨浏览器兼容的。开发人员只需在模块定义中声明events属性。该属性是一个普通的Javascript对象。在该对象中可以声明多个事件绑定。每个事件绑定遵从如下格式:
“事件名@目标元素对应的CSS选择器”:事件监听器函数
例如,要使名为switchLang的函数响应name属性为lang的元素的onchange事件,只需在模块定义的events属性中声明:
“change@[name=lang]”:switchLang
事件绑定声明中“@“后面的CSS选择器遵从jQuery所支持的各个CSS选择器(见:http://api.jquery.com/category/selectors)。
小结
本期介绍了decj的优势及主要特性,并详细介绍了decj的“声明式Javascript文件动态加载”和”声明式跨浏览器事件绑定“这两个特性如何解决Web前端开发中以下常规问题:
- 在页面中引用多个相互存在依赖关系的Javascript文件
- 浏览器事件绑定
下一期将介绍decj的以下特性:
- 声明式表单功能增强(表单自动填充、打包、数据校验和格式化)
- 声明式页面/模块初始化
感谢InfoQ崔康对本文的审校。