本节书摘来自异步社区《JavaScript设计与开发新思维》一书中的第2章,第2.5节,作者:【美】Larry Ullman著,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.5 拼凑一些代码
利用已经介绍过的一些基础知识,我们继续前进,开始接触JavaScript。当然,我不期望你已经了解JavaScript—这是本书的目的—但是下一个示例阐述了JavaScript的易理解性,并且为第2部分的材料(也即正规训练)提供一些背景知识。
2.5.1 开发基本功能
作为一个简单而实用的例子,我们来创建一个登录表单,然后用JavaScript进行验证。在稍后的章节中,你将学习如何为这个表单应用Ajax,但是在这时添加Ajax可能有点过于复杂。首先创建HTML表单,该表单有3个元素:一个电子邮件地址、一个密码和一个提交按钮。下面是最关键的HTML,存储在login.html文件中(图2.8)。
<form action="login.php" method="post" id="loginForm**">
<fieldset>
<legend>Login</legend>
<div><label for="email">Email Address</label>
<input type="email" name="email"
id="email" required></div>
<div><label for="password">Password</label>
<input type="password" name="password"
id="password" required></div>
<div><label for="submit"></label>**<input type=**"**submit**"
value="Login → " id="submit"></div>
</fieldset>
</form>
<script src="js/login.js"></script>
为了简单起见,该页面上除了前述的表单之外,什么也没有。该页面还使用了一个基本的CSS文件以添加一些样式;你可以从本书对应网站上下载该文件(CSS文件将在整个可下载脚本的ch02文件夹中)。
上述表单将被提交给login.php。该脚本将进行如下工作:
- 验证提交的电子邮件地址。
- 验证提交的密码。
- 确认提交的值与以前存储在数据库中的相符。
- 如果匹配,发送一个cookie或者启动一个跟踪用户的会话。
- 将用户重定向到欢迎页面。
如果你还不知道如何自己实现PHP和MySQL,在以后的章节中,你将看到这个表单的所有实际运行状况。这是基线功能,不管浏览器的JavaScript设置和能力如何,这些功能都有效。如果客户能够加载一个HTML页面,这个系统就很好。下一步是对其进行渐进增强。
2.5.2 添加JavaScript层次
在我们所举的特殊例子中,渐进增强意味着JavaScript将被用于在客户端验证表单数据,仅允许在数据通过时将表单提交给服务器(正如图1.4中的注册示例)。
首先要注意这个表单和没有绑定JavaScript的表单之间的唯一区别—每个元素都有name属性和id属性。name值在表单数据提交到服务器端PHP脚本时使用,而JavaScript将使用id属性值。从逻辑上说,这两个值对于每个元素来说都相同。页面上的每个元素(表单或者其他元素)都必须有唯一的id值。
这个渐进增强的页面还使用了一个外部JavaScript文件login. js。它应该包含在HTML页面中body结束标志之前:
<script src="js/login.js"></script>
现在,至少对于本书的这个阶段来说,这个文件的内容有些复杂。为了理解这个文件当中应该有什么样的JavaScript代码,你必须有事件处理的基本知识。
2.5.3 处理事件
正如第1章中所提到的,JavaScript是一种事件驱动语言,这意味着它仅在事件发生之后进行某些工作。事件的例子包括:
- 网页加载;
- 单击一个元素,如按钮或者链接;
- 在表单元素中输入文本;
- 将光标移动到一个元素上(也就是mouseover事件);
- 将光标从一个元素上移开(也就是mouseout事件)。
为了让JavaScript验证一个HTML表单,你必须确定触发验证代码的事件。常用于表单验证的事件如下:
表单提交;
单击提交按钮(这也触发表单提交事件);
改变表单元素的值;
表单元素失去焦点时(不管值是否改变都触发)。
第8章“事件处理”更详细地讨论事件。现在,我们只是在提交的时候验证表单,为此,必须为表单添加一个事件监听器(Event listener)。事件监听器是一个在“这个事件”发生在“这个对象”上时调用的函数。每个对象,不管是整个浏览器窗口还是页面中的具体元素(不管是否为表单元素),都有能够触发的特定事件。被调用的函数通常是你自己定义的。对象、事件类型和函数的组合造成了多种多样的可能性。
为了观察表单的提交事件,我们首先要获得对表单本身的引用。使用 document 对象的 getElement-ById()方法是一种简单而可靠的方式。document对象代表整个HTML内容:从html开始标记到head和body等元素。
Document对象有一个getElementById()方法,它以ID名称为参数,返回对应元素的引用,返回值可以赋值给一个变量供以后使用:
var loginForm = document.getElementById('loginForm');
此时,只要有一个元素(毫无疑问可以是任何类型)的id值为loginForm, loginForm变量将是对该元素的引用。第9章更加详细地研究DOM操纵,但是getElementById()方法非常重要且很容易使用,所以值得在本章里介绍。
有了对表单的引用,就可以用下列代码添加事件监听器:
_element_.on_event _= _function_;
例如:
loginForm.onsubmit = validateForm;
下文的补充说明中将更详细地解释这种语法,但是这一代码行只是说明在loginForm元素遇到一个提交事件时,将调用validate-Form()函数。注意,该函数的名称用在这一赋值语句的右侧,没有引号,结尾也没有圆括号。下面这两种写法都不正确:
loginForm.onsubmit = 'validateForm'; // NO!
loginForm.onsubmit = validateForm(); // NO!
理论上,下一步应该是定义validate-Form()函数,这个函数执行实际的表单验证。遗憾的是,需要先执行另一个步骤。
我将对此加以解释。
从一个服务器请求文档时,客户将按照顺序接受文档的数据。对于HTML页面,这意味着浏览器首先接收DOCTYPE,然后是html开始标记、head标记和head的内容,接着是文件体及其内容,以此类推,直到文档结束。当浏览器遇到对其他必须下载的素材的引用时,就必须相应地进行下载,这些素材包括CSS文件、图像和其他媒体、JavaScript、Flash等。就DOM操纵来说,这一过程很重要,因为浏览器在完全了解HTML 页面(图2.9)之前无法表现DOM。对于JavaScript,这意味着在浏览器加载页面的 HTML 之前,你无法安全地使用 document. getElementById()。
知道何时能够安全地引用DOM元素的最可靠方法是确认浏览器完全加载了整个页面。当然,这是一个事件,可以设置事件监听器,观察事件的发生:
window.onload = init;
提示: 当JavaSript放在靠近文档结尾处时,网站的加载似乎更快,这是因为浏览器在等待JavaScript加载时必须暂停HTML的渲染。
注意: 我打算简化浏览器下载和加载网页的过程,以表达关键的要点。如果你对此过程感到好奇,在网上搜索就能找到很多细节。
对象事件属性
第1章中曾经提到,对象是一个特殊的变量类型,具有预先定义的属性(也就是它自己的内部变量)和方法(即函数)。对象记法(或者圆点)语法用于访问对象的属性和方法。loginForm.onsubmit = validateForm这样的代码将validateForm()函数赋给loginForm对象的onsubmit属性。这似乎有些奇怪,但是这和将数值赋值给一个变量的概念相同: var num = 2; 但在事件监听器的情况下,变量是对象的一个属性,赋给的值是一个函数:稍微复杂一些,但是原理相同。 loginForm对象有一个onsubmit属性,因为loginForm代表一个或者多个触发提交事件的表单元素。这段代码不能用于一个链接,因为链接没有onsubmit属性(但是链接有onclick属性)。引用基于事件的对象属性时,全部使用小写字母:onsubmit而不是onSubmit。 对于上述赋值语句,函数必须与这个事件相关联;因此函数的名称在语句的右侧提供。你不能在函数名称上加引号,这样它将变成一个字符串值而不是函数。你也不能使用functionName()的形式,带上圆括号表示一次实际的函数调用。
这段代码表明,window对象触发加载事件时应该调用init()函数。然后,init()函数可以将事件监听器添加到表单,因为这时进行DOM引用是安全的:
function init() {
var loginForm = document.getElementById('loginForm');
loginForm.onsubmit = validateForm;
}
第 7 章“创建函数”介绍自定义函数所必须了解的内容,但是函数的基础知识实际上很简单。首先,使用function关键字,后面跟上函数的名称和圆括号。(init函数的调用很常见,init是Initialize(初始化)的缩写,该函数用于初始化一些必要的JavaScript和浏览器行为。)该函数的实际代码出现在大括号之间,代表着函数调用时将会执行的任务。
作为附加的保护措施,我们在这里添加对象检测,这样表单的事件监听器将仅在浏览器支持document.getElement-ById()的方法下才被添加:
function init() {
if (document && document.getElementById) {
var loginForm = document.getElementById('loginForm');
loginForm.onsubmit = validateForm;
}
}
现在有两个事件监听器。第一个监听window的load事件,该事件对于每个页面只发生一次,触发时调用init()函数。第二个监听器等待表单的提交,可能发生任意次(也可能一次都不发生)。每当该事件发生时,都调用validateForm()函数。定义该函数是渐进增强的最后一步。
注意: 实际上,浏览器对document对象和getElementById()方法的支持已经超过10年之久,所以对象检测的这种特殊用法实际上并没有必要。
2.5.4 执行验证
validateForm()函数应该验证表单数据并返回表示数据有效性的一个布尔值。如果函数返回true(真),就允许表单提交给服务器端脚本。如果函数返回false(假),那么表单的提交将被阻止。
该函数的外壳如下:
function validateForm() {
}
现在是执行基本验证的时候了,这些验证发生在上述的外壳中。对于电子邮件地址和密码,验证应该检查是否存在某个值(确认电子邮件地址格式有效是可能的,但是需要许多代码)。对于文本输入,可以检查其长度完成简单的验证(也就是证明输入了任意的内容)。这一切从获得每个输入域的引用开始,仍然使用getElementById():
var email = document.getElementById('email');
var password = document.getElementById('password');
提示: 记住,如果网页使用HTML5,而浏览器支持HTML5,将会应用自动化的客户端验证(如前面的插图所示)。
这时,每个变量是对应的表单元素的一个引用。引用变量的value属性就能找到元素的当前值:如email.value和password.value。因为两者都是文本元素,value属性都为一个字符串值,甚至可能是一个空字符串。JavaScript中的所有字符串都有length属性,存储该串中的字符数量。因此,email.value.length是输入到电子邮件输入域的字符数量,然后,可以根据字符数量创建一条简单的条件语句:
if ( (email.value.length > 0) && (password.value.length > 0) ) {
return true;
} else {
return false;
}
注意: 检查元素值的长度对于文本输入域是有效的,其他类型的元素以不同的方式验证。
这里使用的是一个简单的验证例程。除非两个表单元素中都输入了一些内容,否则表单就不会提交给服务器端脚本。但是,除了阻止表单提交以外,用户还应该知道错误的发生。可以用更高级的方式提醒用户,但是现在一个警告框可能就足够了(图2.10和图2.11):
if ( (email.value.length > 0) && (password.value.length > 0) )
{
return true;
} else {
alert('Please complete the form! ');
return false;
}
注意: 客户端验证对于最终用户来说很方便,服务器端验证始终是必要的。
你现在有了一个简单、渐进增强、无干扰的JavaScript,在HTML表单发送到服务器之前进行验证。下面的代码块展示了汇总后的所有代码,以及说明关键部分的一些注释。该脚本有三个顶级的(也就是非嵌套的)组件:
validateForm()函数的定义;
init()函数的定义;
将init()函数注册为window.onload事件监听器。
注意: 因为服务器端脚本login.php尚未编写,在表单通过验证时,浏览器试图访问不存在的文件,你将会看到服务器错误。
但是,出于技术性的原因,这3个组件编写的顺序没有关系,我选择以这种顺序编码是因为:
validateForm()在被init()函数引用之前定义。
init()函数在被赋予window.onload属性之前定义。
同样,这种顺序也不是必需的,但是这对于编码的结构有逻辑上的意义。每个函数也以如下代码开始:
'use strict';
使用这一行代码的原因在“引用严格模式”补充说明中加以解释。
// login.js
// Function called when the form is submitted.
// Function validates the form data and returns a Boolean value.
function validateForm() {
'use strict';
// Get references to the form elements:
var email = document.getElementById('email');
var password = document.getElementById('password');
// Validate!
if ( (email.value.length > 0) && (password.value.length >
0) ) {
return true;
} else {
alert('Please complete the form! ');
return false;
}
} // End of validateForm() function.
// Function called when the window has been loaded.
// Function needs to add an event listener to the form.
function init() {
'use strict';
// Confirm that document.getElementById() can be used:
if (document && document.getElementById) {
var loginForm = document.getElementById('loginForm');
loginForm.onsubmit = validateForm;
}
} // End of init() function.
// Assign an event listener to the window's load event:
window.onload = init;
引用严格模式
JavaScript自己的严格模式和已经讨论过的浏览器严格模式不同,是在你所写的代码中强制更严格的JavaScript行为的一种方式。严格模式在ECMAScript 5中加入,通过在JavaScript中放置如下字符串引用: 'use strict'; 这一行代码在每个脚本开始可以使用一次,但是正如你在本书中所看到的,用作每个函数的第一行更为可靠。 引用严格模式时,JavaScript将与非严格模式中的执行方式略有不同,一般来说,严格模式将: 导致可能有问题的代码生成错误; 改进安全性和性能; 对将在该语言的未来标准中被删除的代码提出警告。 简而言之,严格模式强制你编写更好的代码,这是一件非常非常好的事情。 如果你希望了解严格模式强制形成的变化细节,可以在网络上搜索,但是在现阶段的学习中对你没有太多意义。