7.2.4 客户端脚本错误
到目前为止,我们已了解了来自ASP的错误。然而ASP也经常用于创建包含客户端脚本的网页。如果包含客户端代码的<SCRIPT>元素没有被设置成RUNAT="SERVER"属性,ASP将不考虑服务器,而把网页信息不加改变地传送到客户端。
因此,如果打开了一个ASP网页,并且显示的是一个浏览器错误对话框,就不应该在服务器端寻找ASP程序代码的错误。浏览器看不到ASP程序代码,所以不能识别任何错误,如果有一个对话框出现在客户端,那么在客户端代码中必定有一个错误。
1. 语法错误
如果在网页中的客户端程序代码有语法错误的话,当脚本下载到客户端,浏览器便会出现相应的错误。尽管网页中内容仍可正常载入(除非由这些客户端脚本代码动态装入),但网页停止执行。用户将看到一个包含错误细节的对话框,或者是一个指示网页包含错误的状态条消息。
现代浏览器趋向于隐藏网页脚本错误的细节,而仅在状态条上显示一个小的错误图标。在IE 4.0和IE 5.0中,正常的错误对话框可以通过Internet Options对话框的Advanced页进行设置来激活,如图7-14所示:
图7-14 Advanced页面设置屏幕
处理脚本程序代码中的客户端错误和在服务器端相似,并且通常会更容易些,因为经常可以直接从服务器目录中通过双击来下载网页。一般不需要通过Web服务器和HTTP获得网页来观察浏览器中的结果,其中的唯一不同是一些服务器交互由客户端脚本来完成,如使用RDS的数据绑定或者动态装入。
2. 运行期或语义错误
在客户端脚本中,通常可能会遇到语法错误,也会经常遇到运行期或语义错误。事实上,在客户端,这种现象是很普遍的。因为在客户端不能像服务器端那样对脚本的环境进行控制,不能肯定用户在他们的机器上正运行什么,实际上在服务器上仅能从一些组件如Browser Capabilities中得到大概情况。
所以,使用客户端对象或特殊版本的脚本语言和属性的脚本程序很可能不能正常工作。尽管如此,处理客户端错误和处理服务器端错误是差不多的。
3. 在服务器上创建的客户端程序代码
在错误发生时,作为“客户端对话框对应于ASP错误页面”规则(关于出错的地方)的一个特别的例外是,使用ASP程序代码在服务器上动态地创建客户端程序代码。例如,可能想在ASP中进行求值运算,然后把数据传给运行在客户端的脚本代码,可能最容易的方法是把数据作为一个变量插入脚本代码中:
<%
' get the name of our server from the ServerVariables collection
strServerNameInASP = Request.ServerVariables("SERVER_NAME")
%>
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var strServerName = "<% = strServerNameInASP %>";
…
alert('Server name is: ' + strServerName);
…
// stop hiding code
-->
</SCRIPT>
在客户端,在ASP处理这个页面之后,将得到的是:
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var strServerName = "WROXBOX";
…
alert('Server name is: ' + strServerName);
…
// stop hiding code
-->
</SCRIPT>
可以忽略RUNAT="CLIENT"属性,但是加上这一项可以使得在查看运行代码的ASP网页时更加清楚。
这样,如果在某个位置想把服务器端数据库中的数据加入到一个客户端数组中,可以采用下面的程序实现:
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var arrBooks = new Array(10) //highest available index will be
<% ' start of ASP processing
intIndex = 0
Do While { not at the end of some recordset }
strTitle = { get title from database record }
Response.Write "arrBooks[" & CInt(intIndex) & "] = '" _
& strTitle & "'; " & vbCrlf
intIndex = intIndex +1
{ move to next record in database }
Loop
…
do something here on the client with the array of book titles
…
// stop hiding code
-->
</SCRIPT>
这段服务器端ASP程序代码产生的客户端代码,在客户端运行时创建书名标题数组。同时产生的客户端脚本错误出现在浏览器的错误对话框中。错误的原因是以arrBooks命名的数组是由JavaScript代码运行在客户端时创建的,仅能接受9个书名;而服务器端代码能很可能产生多于9个的书名,具体多少由源数据库中的记录数来决定。这相当于如下客户端代码:
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var arrBooks = new Array(10) //highest available index will be
arrBooks[0] = 'Instant JavaScript';
arrBooks[1] = 'Professional ASP 3.0 Programming';
arrBooks[2] = 'ADO 2.5 Programmers Reference';
…
etc
…
arrBooks[9] = 'ASP Techniques for Webmasters';
arrBooks[10] = 'ASP Programmers Reference'; // <- client-side error occurs here
arrBooks[11] = 'ADSI CDO Programming';
arrBooks[12] = 'Professional MTS and MSMQ Programming';
…
do something here on the client with the array of book titles
…
// stop hiding code
-->
</SCRIPT>
这个页面只有经过修正之后才能正常工作,可以通过增加数组大小,也可以通过控制来自数据库的记录数使其正常工作。
7.3 防止错误
上面已经看到了能够出现的一些不同类型的错误,并且有了一些查找错误的感觉。下面将考虑如何避免把错误引入程序中,尽管不能保证所编写的程序没有错误,但是这里概括的许多技术有助于减少错误数目。
良好的编程习惯
在编程中避免出现错误是和良好的编程习惯相关的,这里有许多工作我们要做,以减少把错误带进网页的可能性。可能有些人因采用某个技术而走向极端,甚至一定程度上在某个特殊问题上因书生气十足而引入了更多的错误。当然编程人员也不可能采用了这里列出的所有技术。
要考虑的主要内容是:
? 代码的格式化和缩进编排。
? 变量显式表明。
? 变量转换为合适的数据类型。
? 使用有意义的变量命名约定。
? 封装脚本。
? 注意潜在的错误情况。
1. 代码的格式化和缩进编排
许多VBScript编程员懒于格式化编排其书写的程序。尽管这并不阻碍程序运行,但这使得查找何处产生了错误变得困难。例如,在前面我们看到的程序中,丢失了一个End If,由于嵌套结构的缩进,错误在哪里是相当明显的:
objCounters.Remove strCounterName
Response.Write "Removed counter " & strCounterName
<--- missing 'End If' should be here
End If
End If
%>
如果程序看起来像下面所示的那样,寻找错误将不是一件易事:
<% if Len(Request.Form("cmdSet")) then
strCounterName=Request.Form("lstSet")
strNewValue=Request.Form("txtSet")
if isnumeric (strnewvalue) then
intNewValue =cint(strNewValue)
objCounters.Set strCounterName, intNewValue
Response.write "Set counter" & strCounterName &" to " & strNewValue
else
Response.write strNewValue &" is not a valid number"
If Len ( Request.Form ("cmdRemove")) then
StrCounterName = Request.Form("lstRemove")
objCounters.Remove strCounterName
Response.write "Removed counter "& strCounterName
end if
End IF
%>
2. 显式表明变量
VBScript支持Option Explicit语句。在一个脚本页面的开头插入Option Explicit语句时,可以避免使用没有用Dim命令(或用于动态数组的ReDim)定义的变量。似乎不需要这么做,因为脚本语言允许通过给一个变量赋值来创建一个需要的变量。然而用Option Explicit进行定义有助于避免错误,特别是那些难以发现的引起脚本产生不正确结果的逻辑错误。
例如,编写如下程序:
<%
' get value for calculation
strSalesTotal = Request.Form("SalesTotal")
curSalesTotal = CCur(strSalesTotal)
sngCommissionPercent = 2.5
' calculate commission payment
sngCommission = curSalesTotal * (sngComissionPercent /100)
%>
运行这段程序不会产生错误(当然,除非用户给销售合计值赋了非法的值)。然而这段程序总是会产生0的结果,因为在程序的最后一行中sngCommissionPercent变量名拼写错了。脚本解释器将产生一个新的变量名(叫作sngComissionPercent),由于没有赋值,在数学计算时返回值总为0。
为了防止这种错误,仅需在程序开头增加Option Explicit语句。
<%
Option Explicit
Dim strSalesTotal
Dim curSalesTotal
Dim sngCommissionPercent
' get value for calculation
strSalesTotal = Request.Form("SalesTotal")
curSalesTotal = CCur(strSalesTotal)
sngCommissionPercent = 2.5
' calculate commission payment
sngCommission = curSalesTotal * (sngComissionPercent /100)
%>
这时,当脚本引擎试图解释程序时将识别出一个语法错误,并且能够指出此变量没有声明,如图7-15所示:
图7-15 显示的错误信息
在JScript中引用一个没有声明的变量将返回一个“Undefined”信息,并且在试图使用变量之前,能够检测到这种情况。
3. 变量转换为合适的数据类型
回头看看前面的程序,可能发现用CCur函数把用户提供的数据转换成了货币型数据类型。在VBScript中,有一系列类似这样的数据变形变换函数,在第3章中有详细的描述。
blnBoolean = Cbool(varVariant) ' converts to a Variant of subtype Boolean
bytByte = Cbyte(varVariant) ' converts to a Variant of subtype Byte
curCurrency = CCur(varVariant) ' converts to a Variant of subtype Currency
datDate = CDate(varVariant) ' converts to a Variant of subtype Date
dblDouble = CDbl(varVariant) ' converts to a Variant of subtype Double
intInteger = CInt(varVariant) ' converts to a Variant of subtype Integer
lngLong = CLng(varVariant) ' converts to a Variant of subtype Long
sngSingle = CSng(varVariant) ' converts to a Variant of subtype Single
strString = CStr(varVariant) ' converts to a Variant of subtype String
如果不能完成变换,也就是说变量内容对新数据类型来说是无效的,便会出现一个运行期错误。然而,如果对数值类型进行变换,我们希望这个数值是有效的,并且能在程序中使用。因此能够检测一个均衡的值对于防止错误的出现是一件“幸事”。
如果想把输入空格作0对待,并且把任何其他无效的输入作为用户错误对待,前面程序变为:
strSalesTotal = Request.Form("SalesTotal")
If Len(strSalesTotal) = 0 Then
' no value entered, so assume zero
curSalesTotal = 0
ElseIf Not IsNumeric(strSalesTotal) Then
' not a valid number, so report an error and stop
Response.Write "The value you entered is
到目前为止,我们已了解了来自ASP的错误。然而ASP也经常用于创建包含客户端脚本的网页。如果包含客户端代码的<SCRIPT>元素没有被设置成RUNAT="SERVER"属性,ASP将不考虑服务器,而把网页信息不加改变地传送到客户端。
因此,如果打开了一个ASP网页,并且显示的是一个浏览器错误对话框,就不应该在服务器端寻找ASP程序代码的错误。浏览器看不到ASP程序代码,所以不能识别任何错误,如果有一个对话框出现在客户端,那么在客户端代码中必定有一个错误。
1. 语法错误
如果在网页中的客户端程序代码有语法错误的话,当脚本下载到客户端,浏览器便会出现相应的错误。尽管网页中内容仍可正常载入(除非由这些客户端脚本代码动态装入),但网页停止执行。用户将看到一个包含错误细节的对话框,或者是一个指示网页包含错误的状态条消息。
现代浏览器趋向于隐藏网页脚本错误的细节,而仅在状态条上显示一个小的错误图标。在IE 4.0和IE 5.0中,正常的错误对话框可以通过Internet Options对话框的Advanced页进行设置来激活,如图7-14所示:
图7-14 Advanced页面设置屏幕
处理脚本程序代码中的客户端错误和在服务器端相似,并且通常会更容易些,因为经常可以直接从服务器目录中通过双击来下载网页。一般不需要通过Web服务器和HTTP获得网页来观察浏览器中的结果,其中的唯一不同是一些服务器交互由客户端脚本来完成,如使用RDS的数据绑定或者动态装入。
2. 运行期或语义错误
在客户端脚本中,通常可能会遇到语法错误,也会经常遇到运行期或语义错误。事实上,在客户端,这种现象是很普遍的。因为在客户端不能像服务器端那样对脚本的环境进行控制,不能肯定用户在他们的机器上正运行什么,实际上在服务器上仅能从一些组件如Browser Capabilities中得到大概情况。
所以,使用客户端对象或特殊版本的脚本语言和属性的脚本程序很可能不能正常工作。尽管如此,处理客户端错误和处理服务器端错误是差不多的。
3. 在服务器上创建的客户端程序代码
在错误发生时,作为“客户端对话框对应于ASP错误页面”规则(关于出错的地方)的一个特别的例外是,使用ASP程序代码在服务器上动态地创建客户端程序代码。例如,可能想在ASP中进行求值运算,然后把数据传给运行在客户端的脚本代码,可能最容易的方法是把数据作为一个变量插入脚本代码中:
<%
' get the name of our server from the ServerVariables collection
strServerNameInASP = Request.ServerVariables("SERVER_NAME")
%>
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var strServerName = "<% = strServerNameInASP %>";
…
alert('Server name is: ' + strServerName);
…
// stop hiding code
-->
</SCRIPT>
在客户端,在ASP处理这个页面之后,将得到的是:
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var strServerName = "WROXBOX";
…
alert('Server name is: ' + strServerName);
…
// stop hiding code
-->
</SCRIPT>
可以忽略RUNAT="CLIENT"属性,但是加上这一项可以使得在查看运行代码的ASP网页时更加清楚。
这样,如果在某个位置想把服务器端数据库中的数据加入到一个客户端数组中,可以采用下面的程序实现:
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var arrBooks = new Array(10) //highest available index will be
<% ' start of ASP processing
intIndex = 0
Do While { not at the end of some recordset }
strTitle = { get title from database record }
Response.Write "arrBooks[" & CInt(intIndex) & "] = '" _
& strTitle & "'; " & vbCrlf
intIndex = intIndex +1
{ move to next record in database }
Loop
…
do something here on the client with the array of book titles
…
// stop hiding code
-->
</SCRIPT>
这段服务器端ASP程序代码产生的客户端代码,在客户端运行时创建书名标题数组。同时产生的客户端脚本错误出现在浏览器的错误对话框中。错误的原因是以arrBooks命名的数组是由JavaScript代码运行在客户端时创建的,仅能接受9个书名;而服务器端代码能很可能产生多于9个的书名,具体多少由源数据库中的记录数来决定。这相当于如下客户端代码:
<SCRIPT LANGUAGE="JScript" RUNAT="CLIENT">
<!-- hide code from older browsers
var arrBooks = new Array(10) //highest available index will be
arrBooks[0] = 'Instant JavaScript';
arrBooks[1] = 'Professional ASP 3.0 Programming';
arrBooks[2] = 'ADO 2.5 Programmers Reference';
…
etc
…
arrBooks[9] = 'ASP Techniques for Webmasters';
arrBooks[10] = 'ASP Programmers Reference'; // <- client-side error occurs here
arrBooks[11] = 'ADSI CDO Programming';
arrBooks[12] = 'Professional MTS and MSMQ Programming';
…
do something here on the client with the array of book titles
…
// stop hiding code
-->
</SCRIPT>
这个页面只有经过修正之后才能正常工作,可以通过增加数组大小,也可以通过控制来自数据库的记录数使其正常工作。
7.3 防止错误
上面已经看到了能够出现的一些不同类型的错误,并且有了一些查找错误的感觉。下面将考虑如何避免把错误引入程序中,尽管不能保证所编写的程序没有错误,但是这里概括的许多技术有助于减少错误数目。
良好的编程习惯
在编程中避免出现错误是和良好的编程习惯相关的,这里有许多工作我们要做,以减少把错误带进网页的可能性。可能有些人因采用某个技术而走向极端,甚至一定程度上在某个特殊问题上因书生气十足而引入了更多的错误。当然编程人员也不可能采用了这里列出的所有技术。
要考虑的主要内容是:
? 代码的格式化和缩进编排。
? 变量显式表明。
? 变量转换为合适的数据类型。
? 使用有意义的变量命名约定。
? 封装脚本。
? 注意潜在的错误情况。
1. 代码的格式化和缩进编排
许多VBScript编程员懒于格式化编排其书写的程序。尽管这并不阻碍程序运行,但这使得查找何处产生了错误变得困难。例如,在前面我们看到的程序中,丢失了一个End If,由于嵌套结构的缩进,错误在哪里是相当明显的:
objCounters.Remove strCounterName
Response.Write "Removed counter " & strCounterName
<--- missing 'End If' should be here
End If
End If
%>
如果程序看起来像下面所示的那样,寻找错误将不是一件易事:
<% if Len(Request.Form("cmdSet")) then
strCounterName=Request.Form("lstSet")
strNewValue=Request.Form("txtSet")
if isnumeric (strnewvalue) then
intNewValue =cint(strNewValue)
objCounters.Set strCounterName, intNewValue
Response.write "Set counter" & strCounterName &" to " & strNewValue
else
Response.write strNewValue &" is not a valid number"
If Len ( Request.Form ("cmdRemove")) then
StrCounterName = Request.Form("lstRemove")
objCounters.Remove strCounterName
Response.write "Removed counter "& strCounterName
end if
End IF
%>
2. 显式表明变量
VBScript支持Option Explicit语句。在一个脚本页面的开头插入Option Explicit语句时,可以避免使用没有用Dim命令(或用于动态数组的ReDim)定义的变量。似乎不需要这么做,因为脚本语言允许通过给一个变量赋值来创建一个需要的变量。然而用Option Explicit进行定义有助于避免错误,特别是那些难以发现的引起脚本产生不正确结果的逻辑错误。
例如,编写如下程序:
<%
' get value for calculation
strSalesTotal = Request.Form("SalesTotal")
curSalesTotal = CCur(strSalesTotal)
sngCommissionPercent = 2.5
' calculate commission payment
sngCommission = curSalesTotal * (sngComissionPercent /100)
%>
运行这段程序不会产生错误(当然,除非用户给销售合计值赋了非法的值)。然而这段程序总是会产生0的结果,因为在程序的最后一行中sngCommissionPercent变量名拼写错了。脚本解释器将产生一个新的变量名(叫作sngComissionPercent),由于没有赋值,在数学计算时返回值总为0。
为了防止这种错误,仅需在程序开头增加Option Explicit语句。
<%
Option Explicit
Dim strSalesTotal
Dim curSalesTotal
Dim sngCommissionPercent
' get value for calculation
strSalesTotal = Request.Form("SalesTotal")
curSalesTotal = CCur(strSalesTotal)
sngCommissionPercent = 2.5
' calculate commission payment
sngCommission = curSalesTotal * (sngComissionPercent /100)
%>
这时,当脚本引擎试图解释程序时将识别出一个语法错误,并且能够指出此变量没有声明,如图7-15所示:
图7-15 显示的错误信息
在JScript中引用一个没有声明的变量将返回一个“Undefined”信息,并且在试图使用变量之前,能够检测到这种情况。
3. 变量转换为合适的数据类型
回头看看前面的程序,可能发现用CCur函数把用户提供的数据转换成了货币型数据类型。在VBScript中,有一系列类似这样的数据变形变换函数,在第3章中有详细的描述。
blnBoolean = Cbool(varVariant) ' converts to a Variant of subtype Boolean
bytByte = Cbyte(varVariant) ' converts to a Variant of subtype Byte
curCurrency = CCur(varVariant) ' converts to a Variant of subtype Currency
datDate = CDate(varVariant) ' converts to a Variant of subtype Date
dblDouble = CDbl(varVariant) ' converts to a Variant of subtype Double
intInteger = CInt(varVariant) ' converts to a Variant of subtype Integer
lngLong = CLng(varVariant) ' converts to a Variant of subtype Long
sngSingle = CSng(varVariant) ' converts to a Variant of subtype Single
strString = CStr(varVariant) ' converts to a Variant of subtype String
如果不能完成变换,也就是说变量内容对新数据类型来说是无效的,便会出现一个运行期错误。然而,如果对数值类型进行变换,我们希望这个数值是有效的,并且能在程序中使用。因此能够检测一个均衡的值对于防止错误的出现是一件“幸事”。
如果想把输入空格作0对待,并且把任何其他无效的输入作为用户错误对待,前面程序变为:
strSalesTotal = Request.Form("SalesTotal")
If Len(strSalesTotal) = 0 Then
' no value entered, so assume zero
curSalesTotal = 0
ElseIf Not IsNumeric(strSalesTotal) Then
' not a valid number, so report an error and stop
Response.Write "The value you entered is