在编写JavaScript和异步JavaScript + XML(Ajax)应用程序时,很容易专注于它们必须提供的交互功能,而忽略了Web应用程序开发的基本原理。 出于多种原因,以一种简洁的方式编写JavaScript和Ajax应用程序很重要。 首先,这样做可以使应用程序的逻辑与内容保持分离,从而使维护应用程序更容易。 此外,它可以确保您的应用程序以其最基本的形式在各种平台和Web浏览器上保持一致的行为,从而使您只需要在添加JavaScript和Ajax功能时就可以担心此问题。 也许最重要的是,以这种方式开发Web应用程序意味着您正在遵循渐进增强的想法,这意味着您的应用程序将支持使用不支持JavaScript或特定JavaScript功能(包括Ajax)的浏览器的用户。 如果到目前为止,您一直在以令人费解的方式编写JavaScript应用程序,那么本文将帮助您发现最佳实践,这些最佳实践可让您创建适用于每个人的Web应用程序,同时为所有可以使用它们的用户提供便利。
非侵入性JavaScript一词的定义较为宽松,但通常被认为是使用一系列良好编程实践来创建网页和应用程序的过程。 其中包括:
- 保持应用程序JavaScript,CSS和HTML元素分离
- 使用JavaScript逐步增强您的应用程序-请勿将JavaScript用于核心功能
- 以减少重复,组织得更好,更易于阅读和维护的方式维护代码结构。
- 遵守网络和可访问性标准
开发这种方式不仅是一种好习惯,而且还可以确保您的应用程序可以使用不同的Web浏览器和设备(即使是功能有限的浏览器)在广泛的受众中使用。 通常,以这种方式构建的应用程序还具有更好的组织和结构,执行速度更快,并且不易出现错误。
在本文中,您将看到应用程序的表示层,样式层和行为层应如何保持隔离,其目标是不使用内联CSS或JavaScript事件处理。 您还将看到一些干扰性JavaScript代码的示例,并发现它们具有的属性,这些属性被认为是不良的编程实践。 然后,您将学习如何纠正这些问题,以一种毫不干扰的方式编写相同的代码,并提供有关这种开发风格的一些最佳实践的指南。 尤其是Ajax应用程序,是不加干扰的代码的危险基础。 仅仅因为您的应用程序具有丰富的Ajax接口并不意味着您不能以逐步增强的方式添加此代码。 您将学习如何使用Ajax功能,从而为无法利用Ajax功能必须提供的流动性的用户提供后备支持。 最后,您将看到一个应用程序的详细示例,该应用程序提供了动态Ajax加载,即使关闭了JavaScript,该加载仍然可以工作。
将行为与内容分开
在Ajax和Web 2.0出现之前,JavaScript被广泛用于诸如客户端表单验证,过渡图像以及显示/隐藏内容之类的基本事情。 因为每个功能都由少量JavaScript代码启用,所以通常的趋势是在HTML文档的<head>
部分的<script>
块中创建几个函数,然后使用HTML元素(例如onclick
上的属性,例如: <input type="button" value="Click Me!" onclick="buttonPressed();">
<input type="button" value="Click Me!" onclick="buttonPressed();">
。
在分离代码的不同部分时,JavaScript并不是唯一的问题。 CSS样式通常也包含在内。 请考虑以下示例: <input type="button" value="Click Me!" onclick="buttonPressed();" style="background-color: #999" />
<input type="button" value="Click Me!" onclick="buttonPressed();" style="background-color: #999" />
<input type="button" value="Click Me!" onclick="buttonPressed();" style="background-color: #999" />
。
这种编码风格通常始于改变元素外观或行为的纯洁努力。 问题是,以这种方式创建的代码越多,它变得越难以管理,并且以不显眼的方式开发代码就越困难。 更改以内联方式编写JavaScript和CSS代码在大型应用程序中是一场噩梦,因为可能需要在许多很多地方进行更改,这使得在此过程中遗漏某些东西非常容易。
而不是内联编码,应将所有JavaScript和CSS代码保留在HTML标记之外。 相反,请使用id
和class
引用属性来简化JavaScript和CSS在DOM中查找和标识这些元素的过程。 对于按钮,您可以如清单1所示重写它。本文在所有示例中都使用Prototype JavaScript库。 您可以使用自己喜欢的库或原始JavaScript替换它。 请参阅相关信息的链接,比较了几种JavaSript框架的文章。
清单1.使您JavaScript和CSS代码脱离HTML标记
// HTML code
<input type="button" value="Click Me!" id="my_button" />
// JavaScript code
$("my_button").observe("click", buttonPressed);
// CSS code
#my_button { background-color: #999; }
正如不要在HTML标记中混合CSS和JavaScript代码很重要,也不要将CSS代码混入JavaScript中也很重要。 例如,清单1中的buttonPressed
函数可能会导致元素样式的更改。 看一下清单2中的示例。
清单2.示例显示了buttonPressed
函数如何无意中更改元素样式的示例
function buttonPressed() {
$("my_div").setStyle({
backgroundColor: "#FF6600",
fontSize: "12px"
})
}
如您所见,此代码直接操作样式,这不好,因为该工作应留给CSS。 相反,应该使用CSS为给定的类名定义样式属性,然后使用JavaScript将该类应用于相关对象。 因此,清单2中的代码可以进行改进,如清单3所示。
清单3.改进代码
// JavaScript code
function buttonPressed() {
var my_div = $("my_div");
if(!my_div.hasClassName("highlight"))
my_div.addClassName("highlight");
}
// CSS code:
.highlight { background-color: #FF6600; font-size: 12px; }
以这种方式分离您的代码将导致一个高度组织的代码结构,使诊断和修复可能出现的任何问题变得更加容易。 还建议您将JavaScript和CSS代码放入外部文件中,而不要将它们包括在HTML代码中(即使它们仅位于<head>
部分中)。 在大多数情况下,这也将有助于加快应用程序的速度,因为外部文件是在第一次访问它们时便被缓存的,不需要在下一页再次下载。
尽管您可以从技术上构建仍在使用内联代码的同时使用渐进增强功能JavaScript应用程序,但这样做会导致难以维护的混乱代码。 为了您自己的缘故,您应该尽可能地将JavaScript,CSS和HTML分开!
实用JavaScript
描述干扰性JavaScript的最好方法是举一个例子。 通常,您会看到以以下方式创建的链接: <a href="#" id="my_link">Click me</a>
。
然后,您可能会在JavaScript代码中找到如下的事件处理程序: $("my_link").observe("click", validateAndSubmit);
。
validateAndSubmit
函数可能包含一些验证规则,如果通过验证规则,则会提交表单或执行其他一些操作。 尽管这似乎是完全无辜的,但如果未启用JavaScript,则该链接将根本无法工作。 浏览器将查找URL#(当然是与您当前所在页面相同的页面的锚点),然后停止。
注意:通常,不应依赖客户端验证,因为很容易关闭或绕过JavaScript,这使其成为验证表单和输入的非常不安全且不可靠的方法。 相反,客户端验证仅应用作改善用户体验和减少发送到服务器的无效表单数量的一种方法。 您应该始终在服务器端验证并清除输入。
在这种情况下,只有在启用JavaScript的情况下,客户端验证才起作用。 修复此链接可能就像创建链接一样简单,如下所示: <a href="page2.html" id="my_link">Click me</a>
。
现在,在您JavaScript代码中,您可以确定是否应遵循该链接(请参见清单4)。
清单4.确定是否应遵循链接
function validateAndSubmit() {
//validation logic here
if(valid) {
return true;
} else {
alert("Error!");
return false;
}
}
在清单4中,如果valid
返回为false,则将打开一个警告框,并且不会跟随该链接,因为事件处理程序返回了一个假值。
不干扰JavaScript的另一种常见用法是在快速跳转下拉列表中。 请参见清单5中的示例。
清单5.快速跳转下拉列表
// HTML code
<select id="my_select">
<option value="">Select...</option>
<option value="http://www.google.com">Google</option>
<option value="http://www.yahoo.com">Yahoo!</option>
<option value="http://www.bing.com">Bing</option>
</select>
// JavaScript code
function goToSearch(e) {
var el = Event.element(e);
if($F(el).length > 0)
window.location = $F(el);
}
document.observe("dom:loaded", function() {
$("my_select").observe("change", goToSearch);
});
同样,在此示例中,如果禁用了JavaScript,则从下拉列表中选择一个项目将不会发生任何事情。 这里的问题是,没有标准HTML后备导航到可用页面之一。 让我们重写此示例,这次使用渐进增强功能包括JavaScript函数(参见清单6)。
清单6.逐步增强
// HTML code:
<form name="redirect" method="get" action="redirect.php">
<select name="my_select" id="my_select">
<option value="">Select...</option>
<option value="http://www.google.com">Google</option>
<option value="http://www.yahoo.com">Yahoo!</option>
<option value="http://www.bing.com">Bing</option>
</select>
<input type="submit" value="Go!" />
</form>
在此示例中,您使用HTML <form>
元素,该元素允许您定义服务器端脚本,如果JavaScript不可用,它将负责重定向。 如果JavaScript已关闭,则当用户单击Go!时! 按钮,表单将提交到redirect.php,并在查询字符串中传递URL。 然后,服务器可以验证是否选择了有效选项,并根据收到的输入重定向输出。
在此示例中,您可以使用JavaScript通过两种方式逐步增强此功能:
- 验证用户已选择搜索引擎
- 执行重定向,而无需将其他HTTP请求发送到Web服务器
让我们继续使用JavaScript来实现这些改进(参见清单7)。
清单7.使用JavaScript实现改进
// JavaScript code
function goToSearch(e) {
Event.stop(e);
var my_select = $F("my_select");
if(my_select.length > 0)
window.location = my_select;
else
alert("You must select a search engine!");
}
document.observe("dom:loaded", function() {
document.redirect.observe("submit", goToSearch);
});
清单7阻止了默认操作的运行(它阻止了表单提交到服务器),然后验证是否已选择了搜索引擎。 如果有,它将用户重定向到所选的URL。 如果还没有,它将显示一个带有错误消息的警告框。
您可能会注意到清单6中的示例添加了Go! 按钮,而原始示例处理<select>
元素本身的onchange
事件。 这里的问题是,您不能提交仅包含此元素而没有提交按钮或JavaScript的表单。 如果您确定没有Go! 按钮,则可以使用JavaScript动态设置按钮的样式,以便在启用JavaScript的浏览器中将其隐藏。 这样,它仍然存在于未启用JavaScript的浏览器中,但被隐藏在具有JavaScript的浏览器中。 然后,您可以将事件处理程序附加到<select>
元素的onchange
事件上,并兼具两者的优点。
编写简洁的代码
为了以一种简洁的方式编写代码,您应该首先开发一个适用于最低公分母的解决方案-一种不支持JavaScript的浏览器。 大多数现代的Web浏览器都具有内置选项来禁用JavaScript或具有可用的插件。 使用此功能可以通过不具备丰富JavaScript的用户的眼光来看您的网站。
首先,编写不带JavaScriptHTML代码,然后编写HTML,使其执行页面需要提供的所有基本功能。 如果需要提交输入数据,请使用具有实际操作属性的<form>
标记。 如果您需要链接到其他项目,请使用老式的超链接;否则,请使用旧版本。 您将无法将事件附加到不适当的元素(例如<img>
或<div>
。 以这种方式构建可运行的应用程序。 它不必很漂亮,也不能提供有史以来最出色的用户体验,但是它必须可以工作。
当您对应用程序或网页无需使用任何JavaScript感到满意时,在提供了设置要满足的基本要求的同时,可以继续使用JavaScript逐步增强页面。 通过以这种方式开发应用程序,您的页面将具有更多的语义含义,因为它更有可能将HTML元素用于其预期目的。 通过这种方式进行开发的另一个巨大好处之一就是,您可能会因为残废而无法使用的功能仍然可以启用。
一个很好的例子是使用不带<form>
标记的表单域,特别是在涉及Ajax应用程序时。 现在很容易不要在应用程序中包含<form>
元素,并使用诸如document.getElementById
类的DOM方法来获取您需要提交的值,然后使用XMLHttpRequest
将它们提交到服务器。 用这种方式构建应用程序的问题是没有考虑默认事件,最终结果是您的应用程序变得不可用。 例如,假设您有一个搜索文本框和一个按钮,该按钮使用本节中描述的XMLHttpRequest
执行站点的搜索,而该过程中未使用<form>
元素。 可以期望,当您在文本框中键入查询时按回车键时,将提交表单并执行搜索。 不幸的是,情况并非如此,您可能会发现自己正在奔波寻找如何捕获按键事件以模仿此功能。 具有讽刺意味的是,如果您首先使用了<form>
元素,则按Return键实际上是可行的。 这只是HTML元素提供的许多被忽略的默认行为的一个例子,开发人员每天仍在继续滥用它们。
让我们看一个简单的示例,毫不费力地编写一段代码。 假设您有一个缩略图图像,并且希望允许用户单击缩略图,并在灯箱中显示该图像的较大版本(出现的一种模式弹出窗口,使用蒙版叠加效果)。 不仅仅是创建<img>
标签,还可以附加一个onclick
事件并调用灯箱以显示大图像。 让我们研究如何完成此工作,以迎合那些没有JavaScript支持的用户。
首先要做的是计划您要做什么。 您要单击图像,然后显示较大的图像,而不离开当前所在的页面。 好的,因此您想添加精美的灯箱效果,但简化了基本要求; 这本质上就是您需要做的。 仅HTML的解决方案非常简单(请参见清单8)。
清单8.仅HTML的解决方案
<a href="large.jpg" id="my_thumb" target="_blank"><img src="thumb.jpg"
width="50" height="50" alt="My Picture" /></a>
清单8包含仅使用简单的旧HTML即可满足的基本要求。 很好,但这并不漂亮。 对于使用JavaScript的用户,您希望将大图像显示在当前窗口的灯箱叠加图中,而不是在新窗口中显示。 您可以按照清单9所示进行操作(假设您的灯箱由openLightbox
函数启动,该函数接受大图像URL作为参数):
清单9. shownLargImage()
函数
function showLargeImage(e) {
Event.stop(e);
var link = Event.element(e).up("a");
openLightbox(link.href);
}
document.observe("dom:loaded", function(e) {
$("my_thumb").observe("click", showLargeImage);
});
为了更进一步,您可以拥有一个缩略图库,并且可能希望为每个缩略图打开一个包含相关大图像的灯箱。 这也很简单。 您可以使用rel
属性(而不是ID)来使用类名,也可以使用灯箱脚本中常用的类名rel
属性(请参见清单10)。
清单10.创建一个缩略图库
// HTML code
<a href="large1.jpg" rel="lightbox" target="_blank"><img src="thumb1.jpg"
width="50" height="50" alt="Picture 1" /></a>
<a href="large2.jpg" rel="lightbox" target="_blank"><img src="thumb2.jpg"
width="50" height="50" alt="Picture 2" /></a>
<a href="large3.jpg" rel="lightbox" target="_blank"><img src="thumb3.jpg"
width="50" height="50" alt="Picture 3" /></a>
// JavaScript code
function showLargeImage(e) {
Event.stop(e);
var link = Event.element(e).up("a");
alert(link.href);
}
document.observe("dom:loaded", function(e) {
$$("a[rel=lightbox]").each(function(thumb) {
thumb.observe("click", showLargeImage);
});
});
如您所见,最终结果是一些非常干净,易读且易于维护的代码。 您可以将事件附加到多个项目,而无需使用不必要的id
属性,也不必以内联方式附加处理程序。 如果您写得太过分,如何处理它的想法令我感到震惊。 我无法显示<a>
标记,在非JavaScript浏览器中没有任何显示。 我可以看到<img>
元素上的内联onclick
处理程序,其中大图像的URL作为参数直接在标记本身中传递。 通过退后一步并尝试确定要实现的目标,我能够提供一种易于访问的解决方案,遵守标准,具有语义含义,并以渐进方式提供整洁的灯箱功能。
不显眼的Ajax
Web 2.0和Ajax应用程序的出现给Web开发人员带来了创建交互式Web应用程序的新挑战。 使用异步HTTP请求检索数据的优势很明显-利用Ajax的应用程序响应速度更快,更易于使用,并且与传统的桌面应用程序相比变得越来越有用(有些人认为,设计的Web应用程序比传统应用程序更加实用和可访问)。
使用Ajax请求的问题在于,在迎合那些没有JavaScript应用程序的奢侈用户时,它会很快使您的视线模糊。 以典型的注册表格为例。 您可以采用多种方法来将这种类型的表单进行Ajaxify 。 您可以自动检查用户名或电子邮件地址是否尚未使用,无需刷新页面即可验证表单,甚至可以提交表单并动态显示结果。
让我们采用一个简单的形式来请求用户名和密码,该形式将异步HTTP请求发送到服务器端脚本,如果该过程成功,则返回“ ok”响应,如果验证成功则返回错误消息问题。 HTML表单的代码形式可能类似于清单11。
清单11.简单形式
<form id="register">
<label for="username">Username</label>
<input type="text" name="username" id="username" />
<label for="password">Password</label>
<input type="password" name="password" id="password" />
<input type="submit" value="Register" id="reg_button" />
</form>
第一个问题应该非常明显。 清单11中的表单没有method或action属性,这意味着当您按下Register按钮时,什么也不会发生。 仅仅因为您可以轻松地使用JavaScript通过其id
属性获取表单并使用Ajax提交表单,并不意味着您应该忘记提交表单的标准方法。 相反,您可以编写服务器端脚本来查找表单中的额外字段。 如果该请求不可用,则您知道该请求是通过常规表格发布的,因此应将整个页面放回原处。 如果将其设置为1,则您知道请求是使用异步Ajax调用发出的,您应该只发送回带有OK
或错误的消息。 然后,您可以在Ajax请求中将该字段的值设置为1,当然,如果浏览器不支持JavaScript,则不会执行该值。
修改后的表格可能类似于清单12。
清单12.修改后的表单
<form id="register" method="post" action="register.php">
<label for="username">Username</label>
<input type="text" name="username" id="username" />
<label for="password">Password</label>
<input type="password" name="password" id="password" />
<input type="submit" value="Register" id="reg_button" />
</form>
现在,您可以采用这种形式,并将您的Ajax请求提交到相同的服务器端脚本,该脚本可以轻松地区分应如何发送响应(如果Ajax字段不存在,则以错误返回到注册页面,或仅输出一条简单消息(如果值为1)。 清单13中显示了执行此操作JavaScript代码(在Prototype库的帮助下)。
清单13.使用该表单将您的Ajax请求提交到服务器端脚本
<script type="text/javascript">
function registerSuccess(transport) {
if(transport.responseText == "ok")
alert("Success!");
else
alert(transport.responseText);
}
function registerFailure(transport) {
alert(transport.status+' '+transport.statusText);
}
function submitUsingAjax(e) {
Event.stop(e);
var options = {
parameters: {
ajax: "1"
},
onSuccess: registerSuccess,
onFailure: registerFailure
}
$("register").request(options);
}
document.observe("dom:loaded", function() {
$("register").observe("submit", submitUsingAjax);
});
</script>
在清单13中,将事件处理程序添加到表单的submit
事件中。 此处理程序可防止触发默认事件(提交表单),将Extra ajax
参数的值设置为1,并使用URL和数据的原始表单发出ajax
请求。 所述registerSuccess
如果接收到成功的HTTP响应代码功能将被调用,或在出现错误的情况下, registerFailure
功能将被调用。
另一个不显眼的Ajax示例
Ajax特别有用的另一种情况是将大量数据分组到页面中,通常称为分页。 例如,如果您有一组搜索结果,而不是一次显示数百个或更多搜索结果,则更有可能显示此数据的较小子集,例如10条记录,并为用户提供选择在页面之间来回移动。 Google的搜索结果就是一个例子。 在页面底部,您可以选择在结果页面之间进行导航。
通常,在页面之间移动的标准方法是将参数传递给加载数据的服务器端脚本,告诉它应输出结果集的哪一页。 问题是,每次用户需要移动到另一个页面时,都必须将请求发送回服务器,这将导致该页面重新加载新的数据集。
使用Ajax,可以确保在获取新的数据页面时,用户不必监视整个页面的重新加载,而只需用新的数据集替换部分页面。 让我们看一个如何工作的例子。
首先,与往常一样,您需要确保迎合那些没有JavaScript的用户。 为此,您需要确保您的应用程序以传统方式工作,并且页面会刷新一组新数据(请参见清单14)。 同样,您将使用将ajax
标志传递到服务器端脚本的概念,以使其知道是常规GET
请求还是Ajax调用来调用它。
清单14.确保该应用程序适用于没有JavaScript的用户
//HTML code
<div id="results">
<ol>
<li>Result 1</li>
<li>Result 2</li>
...
<li>Result 10</li>
</ol>
<a class="paging" href="results.php?page=2">Next Page</a>
<a class="paging" href="results.php?page=10">Last Page</a>
</div>
服务器端脚本result.php会生成一个页面,类似于页面14的清单14中的页面,单击链接会将您带到第二页或最后一页。 如果您不在第1页上,则还会看到“上一页”和“第一页”链接,如果在最后一页上,则不会看到“下一页”或“最后一页”链接。 您甚至可以显示页面列表,以轻松跳转到特定页面。 那么,如何在不对非JavaScript浏览器进行破坏的情况下对此分页部分进行Ajaxify ? 确实很简单,您只需获得所有页面链接的引用,即可在单击默认链接时停止触发默认操作(浏览器将当前页面替换为链接的href
属性中的页面)。 然后,您获取href
属性的值,并将其用作Ajax请求的URL。 最后,标记您的ajax
参数,以便服务器端脚本将知道这是一个Ajax请求(请参见清单15)。
然后,服务器将返回整个页面,或者仅返回要用请求的数据集替换的部分,这取决于是使用Ajax调用还是使用常规GET
请求。 然后,您的函数将用服务器返回HTML替换results
div的内容,其中包含结果的<ol>
列表和分页链接(当您在页面之间移动时,它可能会更改)。
清单15.使分页部分无效
<script type="text/javascript">
function movePageSuccess(transport) {
$("results").innerHTML = transport.responseText;
}
function movePage(e) {
Event.stop(e);
var el = Event.element(e);
var url = el.href;
var options = {
method: "get",
parameters: {
ajax: "1"
},
onSuccess: movePageSuccess
}
}
document.observe("dom:loaded", function() {
$$(".paging").each(function(link) {
link.observe("click", movePage);
});
});
</script>
在那里,这是另一个简单的Ajax示例。 正如您确定的那样,以逐渐增强应用程序的方式来使用Ajax相对简单,而不是将JavaScript强制性化。 另外,通过采用更好的编码实践,您实际上会发现,与使用大量JavaScript破坏性地完成相同的工作相比,以一种轻松的方式使用Ajax更加容易和有效。
结论
本文向您介绍了非侵入性JavaScript的概念,逐步增强功能以及首先不使用JavaScript来设计应用程序的想法,然后将其添加以改善从中受益的用户的体验。 这绝不是关于如何以不干扰用户的方式开发应用程序的详尽资源。 但是,如果像我一样,您花费了大量时间来开发不一定与JavaScript和Ajax兼容的Web应用程序,那么您可能会对采用这种Web开发哲学的好处感到惊讶。
翻译自: https://www.ibm.com/developerworks/web/library/wa-aj-unobtrusive/index.html