在 Forge - 不引人注目的 JavaScript
在过去几年中,JavaScript 经历了许多变化。实现变得更快、更标准化和更稳定。开源 JavaScript 库(如 Prototype 和 Dojo)的开发和增长,帮助掩盖了 JavaScript 实现之间 остался 的许多差异,例如 AJAX 和事件处理。最终的变化发生在开发人员(包括我自己)的思想中,他们现在将 JavaScript 视为一种严肃的应用程序开发语言,而不是用于突出显示图像或进行简单效果的玩具。
大多数 JavaScript 不是在最初读入浏览器窗口时执行,而是在特定事件发生时执行。分配事件处理程序最简单和最常见的方法是在 HTML 本身内部。例如,我们可以创建一个 HTML 表单的提交按钮,如下所示
<form method="POST" action="/action"> <input type="submit" value="Submit the form" /> </form>
当用户单击此按钮时,浏览器使用 method 属性中指定的方法,将表单的内容提交到 form 标签的 action 属性中指定的 URL。但是,我们可以通过向提交按钮添加 onclick 属性来更改此设置
<form method="POST" action="/action"> <input type="submit" value="Submit the form" onclick="alert('hello!'); return false;" /> </form>
有了 onclick 处理程序,该按钮现在会打开一个 JavaScript 警报框(显示“hello”)。此外,由于我们的事件处理程序定义返回 false,因此表单将不会被提交。
当然,我们不必将 JavaScript 字面放在事件处理程序中。我们可以稍后在其他地方定义一个函数——在文档的 <head> 中,或者可能完全在一个外部 JavaScript 文件中
<form method="POST" action="/action"> <input type="submit" value="Submit the form" onclick="do_something(); return false;" /> </form>
现在,这些都不是新鲜事。但是,在“on___”属性中设置事件处理程序存在一些问题。首先,很难为对象上的同一事件分配多个处理程序。
第二个也是更重要的原因是我们的 HTML 变得充满了 JavaScript。直到几年前,HTML 中混合代码和样式信息也很常见,但是严格分离的 MVC 框架的兴起已经从 HTML 中删除了大部分代码,而样式信息现在被放在外部 CSS 文件中。
在过去几年中,一种日益增长的趋势推动了“不引人注目的 JavaScript”。不引人注目的 JavaScript 的支持者认为,通过将 JavaScript 放在单独的文件中,并在单独的文件中定义事件处理程序,代码变得更易于阅读和理解,并被浏览器缓存。通过不引人注目地使用 JavaScript,我们还有机会使我们的 HTML 页面优雅地降级,继续与不支持 JavaScript 的浏览器一起工作。
本月,我们将研究不引人注目的 JavaScript 以及定义函数和事件处理程序的不引人注目的方法。我们还将研究与流行的 Prototype JavaScript 库一起使用的 Lowpro 库,它允许我们编写不引人注目、干净且易于阅读的 JavaScript。
上面,我展示了如何将一段 JavaScript(称为“do_something”)分配给特定 HTML 元素上的事件。此 HTML 表单的更完整版本,以及更多内容和标签,如清单 1 (test.html) 所示。此文件包含一个简单的超链接以及我们的表单。
清单 1. test.html,最简单的版本
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Unobtrusive JavaScript</title> </head> <body> <h1>Unobtrusive JavaScript</h1> <p>A paragraph of text.</p> <p>A <a href="http://www.nytimes.com" id="hyperlink">hyperlink</a> to The New York Times.</p> <form method="POST" action="/action"> <input type="text" name="text_field" id="text_field" /> <input type="submit" value="Submit the form" id="submit_button" /> </form> </body> </html>
我已经讨论了如何通过设置 onclick 属性来处理 onclick 事件。但是,至少还有两种其他方法可以设置此事件处理程序。一种是通过 JavaScript 设置 onclick 属性,将 onclick 视为与超链接或按钮关联的 DOM 元素的属性。使用 Prototype 的 $() 函数,我们可以编写
$('hyperlink').onclick = function() { alert('clicked!'); return false; }
请注意,事件处理程序是一个匿名函数,类似于 Ruby 和 Python 中的“lambda”或 Perl 中的匿名子例程。事件处理函数可以接受一个可选参数,其值将是一个事件对象。例如
$('hyperlink').onclick = function(event) { alert(event); return false; }
有了这段替代代码,警报(至少在 Firefox 中)表明该事件是一个“object MouseEvent”。此对象,像 JavaScript 中的所有对象一样,然后具有许多我们可以查询的属性。例如,pageX 和 pageY 属性指示事件发生时鼠标光标的 X 和 Y 坐标。我们可以通过指定以下内容来查看这些
$('hyperlink').onclick = function(event) { alert(event.pageX + ", " + event.pageY); return false; }
每次单击链接都会给出略有不同的结果,具体取决于单击时鼠标光标的坐标。
当然,我们也可以将非匿名函数定义为我们的事件处理程序
function show_x_and_y(event) { alert(event.pageX + ", " + event.pageY); return false; } $('hyperlink').onclick = show_x_and_y;
请注意,我们的赋值是 show_x_and_y(即函数的名称),而不是 show_x_and_y()(即执行函数的结果)。如果我们想分配同一个函数来处理多个事件,这是一种特别有用的技术。
我们可以处理许多不同的事件。例如,onmouseover 和 onmouseout 事件让我们根据鼠标何时开始或停止指向 DOM 元素来执行函数。因此,我们可以这样做
$('hyperlink').onmouseover = function() { $('the_form').hide(); } $('hyperlink').onmouseout = function() { $('the_form').show(); }
当鼠标指向 test-2.html(清单 2)中的超链接时,HTML 表单消失。当鼠标移开链接时,表单重新出现。这可能不是特别有用,但它确实演示了我们可以定义的事件类型(和事件处理程序)。
清单 2. test-2.html,事件处理程序在 <script> 标签中定义
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Unobtrusive JavaScript</title> </head> <body> <h1>Unobtrusive JavaScript</h1> <script text="text/javascript" src="https://ajax.googleapis.ac.cn/ajax/libs/prototype/ ↪1.6.0.2/prototype.js"></script> <p>A paragraph of text.</p> <p>A <a href="http://www.nytimes.com" id="hyperlink">hyperlink</a> to The New York Times.</p> <form method="POST" action="/action" id="the_form"> <input type="text" name="text_field" id="text_field" /> <input type="submit" value="Submit the form" id="submit_button" /> </form> </body> <script> function show_x_and_y(event) { alert(event.pageX + ", " + event.pageY); return false; } $('hyperlink').onclick = show_x_and_y; $('hyperlink').onmouseover = function() { $('the_form').hide(); } $('hyperlink').onmouseout = function() { $('the_form').show(); } </script> </html> </programlisting>
以这种方式分配事件比使用 onclick 和相关的基于属性的事件处理程序有一些优势。它允许我们将所有事件处理程序定义在一个位置——通常在 HTML 文件的末尾。因此,我们的 HTML 和 JavaScript 之间存在一定的分离。
但是,如果我们想更进一步,将我们所有的 JavaScript 放入一个单独的文件中呢?清单 3 显示了我们的 HTML 文件的新版本,现在称为 test-3.html。我没有将 JavaScript 放在页面底部,而是将其放在一个单独的文件中,称为 atf-events.js(清单 4)。但是,如果您尝试加载此文件,您会很快发现它不起作用。我们在加载文件时收到一个 JavaScript 错误(如果您使用出色的 Firefox Firebug 调试器,则可以清楚地看到和读取),告诉我们 $('hyperlink') 为 null。
清单 3. test-3.html,所有 JavaScript 都已删除并放置在 atf-events.js 中
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Unobtrusive JavaScript</title> </head> <body> <h1>Unobtrusive JavaScript</h1> <script text="text/javascript" src="https://ajax.googleapis.ac.cn/ajax/libs/prototype/ ↪1.6.0.2/prototype.js"></script> <script text="text/javascript" src="atf-events.js"></script> <p>A paragraph of text.</p> <p>A <a href="http://www.nytimes.com" id="hyperlink">hyperlink</a> to The New York Times.</p> <form method="POST" action="/action" id="the_form"> <input type="text" name="text_field" id="text_field" /> <input type="submit" value="Submit the form" id="submit_button" /> </form> </body> </html>
清单 4. atf-events.js,用于 test-3.html 的(损坏的)JavaScript 代码
function show_x_and_y(event) { alert(event.pageX + ", " + event.pageY); return false; } $('hyperlink').onclick = show_x_and_y; $('hyperlink').onmouseover = function() { $('the_form').hide(); } $('hyperlink').onmouseout = function() { $('the_form').show(); }
这怎么可能呢?如果您查看清单 3,您仍然会看到一个 ID 为 hyperlink 的 HTML 元素。而且,我们肯定已经包含了 Prototype 库,所以 $() 应该可以工作。那么,$('hyperlink') 如何返回 null 呢?
答案很微妙,但 JavaScript 程序员众所周知:$('hyperlink') 仅在 ID 为 hyperlink 的 HTML 元素加载后才可用。由于我们的 JavaScript 文件是在定义 hyperlink 元素之前加载的(在文档的 <head> 中),因此 JavaScript 向我们抛出了一个错误。
解决此问题的一种方法是在文件末尾,紧靠结束 </body> 标签之前加载我们的 JavaScript。另一种可能性是在一个函数中定义我们所有的事件处理程序,该函数本身仅在整个文档加载后执行。换句话说,我们定义一个函数 (set_event_handlers) 来定义我们所有的事件处理程序。然后,我们将此函数附加到 window.onload 事件,该事件仅在整个文档加载后执行。清单 5 中显示的代码与清单 4 完全相同,只是该功能包装在 set_event_handlers 函数中,该函数是根据事件调用的。
清单 5. atf-events-2.js,用于 test-3.html 的 JavaScript 代码
function set_event_handlers () { function show_x_and_y(event) { alert(event.pageX + ", " + event.pageY); return false; } $('hyperlink').observe('click', show_x_and_y); $('hyperlink').observe('click', function() { alert('yay!'); return false; }); $('hyperlink').onmouseover = function() { $('the_form').hide(); } $('hyperlink').onmouseout = function() { $('the_form').show(); } } window.onload = set_event_handlers;
我们的事件处理程序现在是不引人注目的。但是,与它们仍然存在一些问题。例如,如果我们想为单个事件分配多个处理程序会发生什么?也就是说,如果我们想为 $('hyperlink').onclick 执行两个函数而不是一个函数怎么办?在我们当前的范例中,我们没有任何选择;要执行两个函数,我们需要将它们都包装到一个函数中,然后使该单个包装器函数成为事件处理程序。
如果我们要加载可能想要将处理程序附加到一个或多个事件的第三方库,这不是一个好主意。相反,我们需要使用不同的范例——一种允许我们将处理程序附加到事件,而不是设置处理程序的范例。
Prototype 允许我们使用 observe 方法来做到这一点,该方法可用于任何扩展元素——包括 $() 和 $$() 函数返回的元素。所以,我们可以说
$('hyperlink').observe('click', show_x_and_y);
由于 Prototype 的 observe 方法的工作方式,我们可以将多个处理程序附加到单个事件
$('hyperlink').observe('click', show_x_and_y); $('hyperlink').observe('click', function() { alert('yay!'); return false;});
当然,由于此代码仍然依赖于 $('hyperlink') 的存在,我们仍然需要将其包装在一个函数中,然后将其附加到 window.onload。(我们也可以将我们的函数附加到 dom:loaded 事件,该事件在 window.onload 之前触发,但思路是相同的。)
另一种解决方案是使用 Lowpro JavaScript 库,该库提供了有助于更轻松地编写不引人注目的 JavaScript 的函数。
通过加载 lowpro.js(在 Prototype 之后,但在任何将使用 Lowpro 的代码之前),我们可以访问 Event.addBehavior 方法,该方法允许我们将一个或多个事件附加到任何 CSS 选择器。清单 6 是我们的 HTML 文件的略微重写版本,以包含 lowpro.js,清单 7 显示了我们如何使用 Event.addbehavior 设置我们的事件处理程序
Event.addBehavior({ '#hyperlink:click' : show_x_and_y, '#hyperlink:mouseover' : function() { $( 'the_form' ).hide() }, '#hyperlink:mouseout' : function() { $( 'the_form' ).show() } });
我们看到 Event.addBehavior 是一个函数,它接受一个参数,即一个 JavaScript 对象(我们可以将其视为哈希)。对象的每个键都将 CSS 选择器(在本例中为 #hyperlink)与事件名称组合在一起,并用冒号分隔两者。请注意,事件名称不包含前导“on”。因此,onmouseover 处理程序对于 Event.addBehavior 称为 mouseover。
清单 6. test-4.html,使用 Lowpro
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Unobtrusive JavaScript</title> </head> <body> <h1>Unobtrusive JavaScript</h1> <script text="text/javascript" src="https://ajax.googleapis.ac.cn/ajax/libs/prototype/1.6.0.2/ ↪prototype.js"></script> <script text="text/javascript" src="lowpro.js"></script> <script text="text/javascript" src="atf-events-3.js"></script> <p>A paragraph of text.</p> <p>A <a href="http://www.nytimes.com" id="hyperlink">hyperlink</a> to The New York Times.</p> <form method="POST" action="/action" id="the_form"> <input type="text" name="text_field" id="text_field" /> <input type="submit" value="Submit the form" id="submit_button" /> </form> </body> </html>
清单 7. atf-events-3.js,使用 Lowpro 的事件添加代码
function show_x_and_y(event) { alert(event.pageX + ", " + event.pageY); return false; } Event.addBehavior({ '#hyperlink:click' : show_x_and_y, '#hyperlink:mouseover' : function() { $( 'the_form' ).hide() }, '#hyperlink:mouseout' : function() { $( 'the_form' ).show() } });
正如您在清单 7 中看到的,Event.addBehavior 会自动将我们的事件处理程序定义包装在等待整个页面加载的代码中。因此,例如,我们不再需要设置 document.onload。
最后,CSS 选择器代码意味着我们可以同时在多个元素上设置事件。如果我们想要所有段落、所有表头甚至所有图像,我们可以使用 Lowpro 快速轻松地做到这一点。Lowpro 允许我们显着减少我们编写的事件处理代码量,将其保存在一个位置并从我们可能首先考虑放置它的 HTML 文件中删除它。
我应该补充一点,Lowpro 过去也包含 DOM 操作例程,允许我们使用各种便利函数添加和修改页面元素。但是,最新版本的 Prototype 已经包含了此功能,这使得 Lowpro 可以专注于 Prototype 未解决的行为。
不引人注目的 JavaScript 是一种越来越流行的 JavaScript 工作方式,尤其是在定义事件处理程序时。与原始 JavaScript 相比,Prototype 使事件处理更容易,但 Lowpro 库使其甚至更容易。使用 Lowpro,可以非常简单地将事件处理程序分配给文档中元素的任意组合,而无需弄乱我们的 HTML 页面或担心页面何时加载。
资源
David Flanagan 的JavaScript: 权威指南是 JavaScript 程序员的优秀资源,包括教程和参考部分。Douglas Crockford 最近的书籍JavaScript: 好的部分篇幅短得多,但也很出色,并就我们应该避免使用 JavaScript 的哪些部分提供了有用的建议。这两本书都由 O'Reilly 出版。自从阅读了 Crockford 的作品后,我对 JavaScript 的看法(和使用)有了极大的改善,让我可以更专注于编写代码,而减少关注与 JavaScript 规范或实现相关的问题。
您可以在其主页 www.prototypejs.org 上阅读有关 Prototype 的更多信息。我也很喜欢 Christophe Porteneuve 编写并由 Pragmatic Programmers 出版的书籍 Prototype 和 Scriptaculous。
最后,Lowpro 库由 Dan Webb 编写和分发,最好在他的博客 www.danwebb.net/2006/9/3/low-pro-unobtrusive-scripting-for-prototype 中描述。并且,用于讨论 Lowpro 的 Google 网上论坛位于 groups.google.co.uk/group/low-pro。
Reuven M. Lerner 是一位长期的 Web/数据库开发人员和顾问,是西北大学学习科学专业的博士候选人,研究在线学习社区。在芝加哥地区生活四年后,他最近(与他的妻子和三个孩子)返回他们在以色列莫迪因的家。