将所有的JavaScript编写在一个分离的文件中,是强化视图分离的良好开端。但是即使这样做,如果不注意,仍然可能使视图混入逻辑角色(即模型和控制器)。如果将JavaScript事件处理函数内嵌在页面中,例如:
<div class='importButton'
onclick='importData("datafeed3.xml", mytextbox.value);'/>
那样就是将业务逻辑硬编码在视图中。什么是datafeed3?mytextbox的值和它有什么关系?为什么importData()有两个参数,它们的意思是什么?页面设计师不需要知道这些事情。
importData()是一个业务逻辑函数。按照MVC的法则,视图和模型不应该直接通信,一种解决方案是使用额外的层来分离它们。如果将DIV标签重写为:
<div class='importButton' onclick='importFeedData()'/>
并且将事件处理函数定义为:
function importFeedData(event){
importData("datafeed3.xml", mytextbox.value);
}
那么参数就被封装在了importFeedData()函数中,而不是一个匿名的事件处理函数中。这允许我们在其他地方重用这个功能,同时分离关注点,保持代码的DRY(冒着重复我自己的风险,DRY的意思是“不重复你自己”)。
然而控制器仍然被嵌入在HTML中,这使得很难在一个大型应用中找到它。
为了保持控制器和视图分离,我们可以采用编程方式添加事件。除了使用嵌入的事件处理函数,我们还可以指定某种类型的记号,它随后会被代码获得。有几种方法来做这个记号。可以给元素附加唯一的ID,以每个元素为基础指定事件处理函数。将HTML改写为:
<div class='importButton' id='dataFeedBtn'>
下面的代码作为window.onload回调的一部分来执行,例如:
var dfBtn=document.getElementById('dataFeedBtn');
dfBtn.onclick=importFeedData;
如果希望对多个事件处理函数执行相同的操作,我们需要使用某种不唯一的记号,一种简单的方法是定义一个额外的CSS类。
1. 使用CSS间接添加事件
让我们来看一个简单的例子,在这里将鼠标事件绑定在虚拟的音乐键盘的键上。代码清单4-1定义了一个包含原始文档结构的简单页面。
代码清单4-1 musical.html
<!DOCTYPE html
PUBLIC "-//W
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Keyboard</title>
<link rel='stylesheet' type='text/css' href='musical.css'/>
<script type='text/javascript' src='musical.js'></script>
<script type='text/javascript'>
window.onload=assignKeys;
</script>
</head>
<body>

我们声明了页面以符合严格定义的XHTML,仅仅为了显示这是可以做到的。我们为表示键盘的keyboard元素分配了唯一的ID,但是没有为表示键的元素分配ID。注意,每一个指定的键
都有两个样式。musicalButton对于所有的键是通用的,另外一个单独的样式通过音符来区别它们。这些样式在样式表中单独定义(代码清单4-2)。
代码清单4-2 musical.css


![]()
样式musicalButton定义了每个键的通用属性,特定于音符的样式仅仅定义了每个键的颜色。注意,尽管顶级文档元素使用显式的像素精度来定位,但是通过float样式属性应用浏览器内建的布局引擎,可以将各个键分布在一条水平线上。
2. 绑定事件处理函数代码
JavaScript文件(代码清单4-3)采用编程方式将事件绑定到键上。
代码清单4-3 musical.js

window.onload调用了assignKeys()函数(可以在这个文件中直接定义window. onload,但是这限制了它的可移植性)。通过唯一的ID来发现keyboard元素,然后使用getElementsByTagName()遍历访问其内部所有的DIV元素。这需要知道一些关于页面结构的知识,但是它允许页面设计师自由地在页面中将键盘DIV以希望的方式任意移动。
表示键的DOM元素返回一个单独的字符串作为className属性。我们使用内建的String. split函数将其转换为一个数组,并且检查元素是否是musicalButton类。之后读取样式的另一部分——它代表了键所演奏的音符——并且作为一个额外的属性附加在DOM节点上,这个属性可以被事件处理函数获得。
通过Web浏览器演奏音乐需要相当高的技巧,在这里,我们仅仅对键盘下的控制台进行了编程,用innerHTML已经足够了。图4-4显示了活动中的音乐键盘。这里我们实现了很好的角色分离,假设页面设计师去掉了页面上某个地方的键盘和控制台的DIV标签,只要页面包括了样式表和JavaScript,应用程序仍然可以工作,偶然打破事件逻辑的风险是很小的。HTML页面有效地成为了一个模版,我们向其中注入了变量和逻辑,这提供了一个保持逻辑与视图相分离的好方法。我们已经手工完成了这个例子,以此来示范工作机制的细节。在生产环境中,你可能喜欢使用几个解决了同样问题的第三方库。

图4-4 运行于浏览器中的音乐键盘应用。顶部的彩色区域被映射到音符上,当鼠标在上面移动时,音符打印在下面的控制台区域
Rico框架(www.openrico.org)有一个Behavior对象的概念,它以DOM树的特定部分为目标,为它们添加交互性。
类似的分离HTML标记和交互性的方法可以通过Ben Nolan的Behaviour库来实现(参见本章“资源”一节)。这个库允许基于CSS选择器规则将事件处理函数代码分配给DOM元素(见第2章)。在之前的例子中,assignKeys()函数以keyboard作为id采用编程方式选择文档元素,然后使用DOM处理方法得到它直接包含的所有DIV元素。我们可以使用一个CSS选择器来表达:
#keyboard div
使用CSS选择器可以给所有的keyboard元素设置样式。使用Behaviour.js库,也可以用相同的方法应用事件处理函数,如下:

大部分逻辑与前面的例子是一样的,但是对CSS选择器的使用提供了一种采用编程方式定位DOM元素的简明的替代方法,特别是当需要立即添加几个行为的时候。
这种方法保持了逻辑与视图的分离,但是它也可能将视图和逻辑混在一起,下面我们将会看到这一点。







