第2章 在网页中使用JavaScript
与能够独立执行的C/C++等传统语言不同,执行JavaScript代码需要HTML网页环境。在当初开发JavaScript时,Netscape把JavaScript定位为嵌入式Web脚本语言,这种做法被保留了下来,并被正式纳入HTML规范当中。本章将详细介绍如何在网页中编写JavaScript代码并执行,同时介绍如何在浏览器中进行JavaScript代码调试和错误处理。
【学习重点】
▲ 灵活使用<script>标签
▲ 了解JavaScript脚本存放位置
▲ 会用<script>标签
▲ 了解JavaScript执行顺序
▲ 了解JavaScript错误处理机制(选学)
▲ 了解JavaScript代码调试方法(选学)
2.1 使用<script>标签
通常情况下,在Web页面中使用JavaScript有两种方法,一种是在页面中直接嵌入JavaScript代码,另一种是链接外部JavaScript文件。
在HTML页面中嵌入JavaScript脚本需要使用<script>标签,用户可以在<script>标签中直接编写JavaScript代码,或者单独编写JavaScript文件,然后通过<script>标签导入。
2.1.1 编写第一个JavaScript程序
下面通过示例演示如何使用<script>标签的两种方式(直接在页面中嵌入JavaScript代码和链接外部JavaScript文件)。
【示例1】直接在页面中嵌入JavaScript代码。
第1步,新建HTML文档,保存为test.html。然后在标签内插入一个<script>标签。
第2步,为<script>标签指定type属性值为"text/JavaScript"。现在的浏览器默认<script>标签的类型为JavaScript脚本,因此省略type属性,依然能够被正确执行,但是考虑到代码的兼容性,建议定义该属性。
第3步,直接在<script>标签内部输入JavaScript代码:
上面JavaScript脚本先定义了一个hi()函数,该函数被调用后会在页面中显示字符"Hello,World!"。document表示DOM网页文档对象,document.write()表示调用Document对象的write()方法,在当前网页源代码中写入HTML字符串"
Hello,World!
"。调用hi()函数,浏览器将在页面中显示一级标题字符"Hello,World!"。
第4步,保存网页文档,在浏览器中预览,显示效果如图2-1所示。
图2-1 第一个JavaScript程序
提示:包含在<script>标签内的JavaScript代码被浏览器从上至下依次解释。
当使用<script>标签嵌入JavaScript代码时,不要在代码中的任何地方输出" script>"字符串。例如,浏览器在加载下面所示的代码时就会产生一个错误:
错误原因:当浏览器解析到字符串" script>"时,会结束JavaScript代码段的执行。
解决方法:
使用转义字符把字符串" script>“分成两部分来写就不会造成浏览器的误解。
【示例2】链接外部JavaScript文件。
第1步,新建文本文件,保存为test.js。注意,扩展名为.js,它表示该文本文件是JavaScript类型的文件。
提示:使用<script>标签包含外部JavaScript文件时,默认文件类型为JavaScript,因此.js扩展名不是必需的,浏览器不会检查包含JavaScript的文件的扩展名。在高级开发中,使用JSP、PHP或其他服务器端语言动态生成JavaScript代码时可以使用任意扩展名,如果不使用.js扩展名,用户应确保服务器能返回正确的MIME类型。
第2步,打开test.js文本文件,在其中编写下面代码,定义简单的输出函数。
在上面代码中,alert()表示Window对象的方法,调用该方法将弹出一个提示对话框,显示参数字符串"Hello,World!”。
第3步,保存JavaScript文件,注意与网页文件的位置关系。这里保存JavaScript文件位置与调用该文件的网页文件位于相同目录下。
第4步,新建HTML文档,保存为test1.html。然后在标签内插入一个<script>标签。定义src属性,设置属性值为指向外部JavaScript文件的URL字符串。代码如下:
<script type=“text/JavaScript” src=“test.js”> script>
第5步,在上面<script>标签下一行继续插入一个<script>标签,直接在<script>标签内部输入JavaScript代码,调用外部JavaScript文件中的hi()函数。
第6步,保存网页文档,在浏览器中预览,显示效果如图2-2所示。
图2-2 调用外部函数弹出提示对话框
提示:定义src属性的<script>标签不应再包含JavaScript代码。如果嵌入了代码,则只会下载并执行外部JavaScript文件,嵌入代码会被忽略。
<script>标签的src属性可以包含来自外部域的JavaScript文件。例如:
<script type=“text/JavaScript” src=“http://www.sothersite.com/test.js”> script>
这些位于外部域中的代码也会被加载和解析。因此在访问自己不能控制的服务器上的JavaScript文件时要小心,防止恶意代码,或者防止恶意人员随时可能替换JavaScript文件中的代码。
【拓展】HTML为<script>定义了6个属性,简单说明如下。
☑ async:可选。表示应该立即下载脚本,但不应妨碍页面中其他操作,如下载其他资源或等待加载其他脚本。该功能只对外部JavaScript文件有效。
☑ charset:可选。表示通过src属性指定的代码的字符集。由于大多数浏览器会忽略它的值,因此很少使用。
☑ defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。该属性只对外部JavaScript文件有效。IE 7及更早版本对嵌入的JavaScript代码也支持这个属性。
☑ language:已废弃。原来用于表示编写代码使用的脚本语言,如JavaScript、JavaScript l.2或VBScript。大多数浏览器会忽略这个属性,不建议再使用。
☑ src:可选。表示包含要执行代码的外部文件。
☑ type:可选。可以看成是language的替代属性,表示编写代码使用的脚本语言的内容类型(也称为MIME类型)。虽然text/JavaScript和text/ecmascript已经不被推荐使用,但人们一直习惯使用text/JavaScript。服务器在传送JavaScript文件时使用的MIME类型通常是application/x-JavaScript,但在type中设置这个值可能导致脚本被忽略。另外,在非IE浏览器中还可以使用application/JavaScript和application/ecmascript。考虑到约定俗成和浏览器最大限度的兼容性,目前在客户端,type属性值一般使用text/JavaScript。不过,这个属性并不是必需的,如果没有指定这个属性,则其默认值仍为text/JavaScript。
2.1.2 脚本位置
所有<script>标签都会按照它们在HTML中出现的先后顺序依次被解析。在不使用defer和async属性的情况下,只有在解析完前面<script>标签中的代码之后,才会开始解析后面<script>标签中的代码。
【示例1】在默认情况下,所有<script>标签都应该放在页面头部的标签中。
这样就可以把所有外部文件(包括CSS文件和JavaScript文件)的引用都放在相同的地方。但是,在文档的标签中包含所有JavaScript文件,意味着必须等到全部JavaScript代码都被下载、解析和执行完成以后,才能开始呈现页面的内容。如果页面需要很多JavaScript代码,这样无疑会导致浏览器在呈现页面时出现明显的延迟,而延迟期间的浏览器窗口中将是一片空白。
【示例2】为了避免延迟问题,现代Web应用程序一般都把全部JavaScript引用放在标签中页面的内容后面。
这样,在解析包含的JavaScript代码之前,页面的内容将完全呈现在浏览器中,同时会感到打开页面的速度加快了。
2.1.3 延迟执行脚本
为了避免脚本在执行时影响页面的构造,HTML为<script>标签定义了defer属性。defer属性能够迫使脚本被延迟到整个页面都解析完毕后再运行。因此,在<script>标签中设置defer属性,相当于告诉浏览器虽然可以立即下载JavaScript代码,但延迟执行。
【示例】在下面的示例中,虽然把<script>标签放在文档的标签中,但其中包含的脚本将延迟到浏览器遇到标签后再执行。
HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于DOMContentLoaded事件执行。在实际应用中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoaded事件触发前执行,因此最好只包含一个延迟脚本。
提示:defer属性只适用于外部脚本文件。这一点在HTML5中已经明确规定,因此支持HTML5的实现会忽略给嵌入脚本设置的defer属性。IE 4~IE 7还支持对嵌入脚本的defer属性,但IE 8及之后版本则完全支持HTML5规定的行为。
IE 4、Firefox 3.5、Safari 5和Chrome是最早支持defer属性的浏览器。其他浏览器会忽略这个属性。因此,把延迟脚本放在页面底部仍然是最佳选择。
注意:在XHTML类型的文档中,defer属性应该定义为defer=“defer”。
2.1.4 异步响应脚本
HTML5为<script>标签定义了async属性。这个属性与defer属性类似,都用于改变外部脚本的行为。同样与defer类似,async只适用于外部脚本文件,并告诉浏览器立即下载文件。但与defer不同的是,标记为async的脚本并不保证按照指定它们的先后顺序执行。
【示例】在下面的代码中,第二个脚本文件test2.js可能会在第一个脚本文件test1.js之前执行。因此,用户要确保两个文件之间没有逻辑顺序的关联,互不依赖是非常重要的。
指定async属性的目的是不让页面等待两个脚本文件下载完后再执行,从而异步加载页面其他内容。
提示:异步响应的脚本一定会在页面的load事件前执行,但可能会在DOMContentLoaded事件触发之前或之后执行。异步脚本不要在加载期间修改DOM。
支持异步脚本的浏览器包括Firefox 3.6+、Safari 5和Chrome。在XHTML文档中,要把async属性设为async=“async”。
2.1.5 在XHTML中使用JavaScript脚本
XHTML(EXtensible HyperText Markup Language)表示可扩展超文本标记语言,是将HTML作为XML的应用而重新定义的一个标准。编写XHTML代码的规则要比编写HTML严格得多,而且直接影响能否在嵌入JavaScript代码时使用<script/>标签。
提示:将页面的MIME类型指定为application/xhtml+xml.的情况下会触发XHTML模式,当然并不是所有浏览器都支持这种方法。
【示例】下面的代码块虽然在HTML中是有效的,但在XHTML中则是无效的。
在HTML中,有特殊的规则用以确定<script>标签中的哪些内容可以被解析,但这些特殊的规则在XHTML中不适用。例如,上面代码中比较语句a
避免在XHTML中出现类似语法错误的方法如下。
方法一,使用相应的HTML转码(&1t;)替换代码中所有的小于号(<),替换后的代码如下:
虽然这样可以让代码在XHTML中正常运行,但却导致代码不好理解了。
方法二,使用一个CData片段来包含JavaScript代码。在XHTML中,CData片段是文档中的一个特殊区域,这个区域中可以包含不需要解析的任意格式的文本内容。因此,在CData片段中就可以使用任意字符,且不会导致语法错误。引入CData片段后的JavaScript代码块如下:
在兼容XHTML的浏览器中,这个方法可以解决问题。但实际上,还有不少浏览器不兼容XHTML,因而不支持CData片段。这时可以使用JavaScript注释将CData标记注释掉。
这样在所有现代浏览器中都可以正常使用,它能通过XHTML验证,而且能够兼容XHTML之前的浏览器。
2.1.6 兼容不支持JavaScript的浏览器
最早引入<script>标签时,与传统HTML的解析规则存在冲突。由于要对这个标签应用特殊的解析规则,因此在那些不支持JavaScript的浏览器中就会导致问题,即会把<script>标签的内容直接输出到页面中,因而会破坏页面的布局和外观。
Netscape与Mosaic协商并提出了一个解决方案,让不支持<script>标签的浏览器能够隐藏嵌入的JavaScript代码。这个方案就是把JavaScript代码包含在一个HTML注释中。
【示例】下面的代码给脚本加上HTML注释后,不支持JavaScript的浏览器就会忽略<script>标签中的内容,而那些支持JavaScript的浏览器在遇到这种情况时,则必须进一步确认其中是否包含需要解析的JavaScript代码。
虽然这种注释JavaScript代码的格式得到了所有浏览器的认可,也能被正确解释,但由于所有浏览器都已经支持JavaScript,因此也就没有必要再使用这种格式了,也不再推荐使用这种方法。
在XHTML模式下,因为脚本包含在XML注释中,所以脚本会被忽略。
2.2 比较嵌入代码与链接脚本
在HTML中嵌入JavaScript代码虽然没有问题,但一般认为最好的做法还是尽可能使用外部文件来包含JavaScript代码。不过,并不存在必须使用外部文件的硬性规定,但支持使用外部文件有如下优点。
☑ 可维护性:遍及不同HTML页面的JavaScript会造成维护问题。但把所有JavaScript文件都放在一个文件夹中,维护起来就轻松多了。而且开发人员因此也能够在不触及HTML标记的情况下,集中精力编辑JavaScript代码。
☑ 可缓存:浏览器能够根据具体的设置缓存链接的所有外部JavaScript文件。也就是说,如果有两个页面都使用同一个文件,那么这个文件只需下载一次。因此,最终结果就是能够加快页面加载的速度。
☑ 适应未来:通过外部文件来包含JavaScript无须使用前面提到的XHTML或注释hack,HTML和XHTML包含外部文件的语法是相同的。
2.3 使用<noscript>标签</no
早期浏览器不支持JavaScript,为了确保页面平稳兼容,创造了一个<noscript>标签,用以在不支持JavaScript的浏览器中显示替代的内容。这个元素可以包含能够出现在文档中的任何HTML标签,但<script>标签除外。包含在<noscript>标签中的内容只有在下列情况下才会显示出来。</no</no
☑ 浏览器不支持脚本。
☑ 浏览器支持脚本,但脚本被禁用。
符合上述任何一个条件,浏览器都会显示<noscript>中的内容。而在除此之外的其他情况下,浏览器不会呈现<noscript>中的内容。</no</no
【示例】请看下面这个简单的例子。
这个页面在脚本无效的情况下会显示一行文字:当前浏览器不支持JavaScript。而在启用了脚本,且支持JavaScript的浏览器中会显示:当前浏览器支持JavaScript。
2.4 JavaScript执行顺序
JavaScript解释过程包括两个阶段:预处理(也称编译)和执行。在预编译期,JavaScript解释器将完成对JavaScript代码的预处理操作,把JavaScript代码转换成字节码;在执行期,JavaScript解释器把字节码生成二进制机器码,并按顺序执行,完成程序设计的任务。
提示:预编译包括词法分析和语法分析。词法分析主要对JavaScript脚本进行逐一分析,检查脚本是否符合JavaScript规范,是否存在语法错误;语法分析主要是把从程序中收集的信息存储到数据结构中,如符号表和语法树。
☑ 符号表:存储程序中所有符号的一个表,包括所有的字符串、直接量、变量名和函数名等。
☑ 语法树:构建程序结构的一个树形表示,并将使用这个树形结构来生成中间代码。
2.4.1 正常执行顺序
HTML文档在浏览器中的解析过程是:按文档流从上到下逐步解析页面结构和信息。JavaScript代码作为嵌入的脚本应该也算作HTML文档的组成部分,所以JavaScript代码在装载时的执行顺序也是根据<script>标签的出现顺序来确定的。
【示例1】浏览下面文档页面,会看到代码是从上到下逐步被解析的。
对于导入外部的JavaScript文件脚本,也将按照其<script>标签在文档中出现的顺序来执行,而且执行过程是文档装载的一部分。不会因为是外部JavaScript文件而延期执行。
【示例2】把上面文档中的头部和主体区域的脚本移到外部JavaScript文件中,然后通过src属性导入进来。继续预览页面文档,会看到相同的执行顺序。
2.4.2 预编译提前
当JavaScript引擎解析脚本时,它会在预编译期对所有声明的变量和函数预先进行处理。
【示例1】当JavaScript解释器执行下面脚本时不会报错。
由于变量声明是在预编译期被处理的,在执行期间对于所有代码来说,都是可见的。但是,执行上面代码,提示的值是undefined,而不是1。因为变量初始化过程发生在执行期,而不是预编译期。在执行期,JavaScript解释器是按着代码先后顺序进行解析的,如果在前面代码行中没有为变量赋值,则JavaScript解释器会使用默认值undefined。由于在第二行中为变量a赋值了,所以在第三行代码中会提示变量a的值为1,而不是undefined。
【示例2】下面的示例在函数声明前调用函数也是合法的,并能够被正确解析,所以返回值为1。
【示例3】如果按下面方式定义函数,则JavaScript解释器会提示语法错误。
上面示例中定义的函数仅作为值赋值给变量f。在预编译期,JavaScript解释器只能够为声明变量f进行处理,而对于变量f的值,只能等到执行期时按顺序进行赋值,自然就会出现语法错误,提示找不到对象f。
提示:声明变量和函数可以在文档任意位置,但是良好的习惯应该是在所有JavaScript代码之前声明全局变量和函数,并对变量进行初始化赋值。在函数内部也是先声明变量,后引用。
2.4.3 代码块的执行顺序
代码块就是使用<script>标签分隔的代码段。
【示例1】下面两个<script>标签分别代表两个JavaScript代码块。
JavaScript解释器在执行脚本时,是按块来执行的。浏览器在解析HTML文档流时,如果遇到一个<script>标签,则JavaScript解释器会等到这个代码块都加载完后,先对代码块进行预编译,然后再执行。执行完毕后,浏览器会继续解析下面的HTML文档流,同时JavaScript解释器也准备好处理下一个代码块。
【示例2】如果在一个JavaScript块中调用后面块中声明的变量或函数就会提示语法错误。例如,当JavaScript解释器执行下面代码时就会提示语法错误,显示变量a未定义,对象f找不到,如图2-3所示。
图2-3 错误的代码块顺序
提示:JavaScript是按块执行的,但是不同块都属于同一个全局作用域,块之间的变量和函数是可以共享的。
2.4.4 事件响应顺序
JavaScript响应操作是通过事件驱动的模式来实现的,由于事件发生的不确定性,所以JavaScript事件响应的顺序也是不确定的。
【示例】针对2.4.3节第2个示例的错误响应,可以把访问第2块代码中的变量和函数的代码放在页面初始化事件函数中,这样就不会出现语法错误了。
onload事件只有在文档加载完毕才会响应。因此为了运行安全,一般都设计在页面初始化完毕之后才允许JavaScript代码执行,这样就可以避免因为代码加载延迟对JavaScript执行的影响。同时也避开了HTML文档流对于JavaScript执行的限制。
提示:除了页面初始化事件外,用户还可以通过各种交互事件来改变JavaScript代码的执行顺序,如鼠标事件、键盘事件,以及时钟触发器等方法。
2.4.5 动态输出脚本字符串
使用document对象的write()方法输出JavaScript脚本时,这些动态输出的脚本的执行顺序也不同。
【示例1】JavaScript脚本输出的代码字符串会在输出后马上被执行。
document.write(’<script type=“text/JavaScript”>’);
document.write(‘f();’);
document.write(‘function f(){’);
document.write(’ alert(1);’);
document.write(’}’);
document.write(’</script>’);
运行代码,document.write()方法先把输出的JavaScript字符串写入到标签所在的文档位置,浏览器在解析完document.write()所在文档内容后,继续解析document.write()输出的内容,然后才按顺序解析后面的HTML文档。
提示:使用document.write()方法输出的JavaScript字符串必须放在同时被输出的<script>标签中,否则JavaScript解释器因为不能识别这些合法的JavaScript代码,而作为普通的字符串显示在页面文档中。
【示例2】下面的代码就会把JavaScript代码显示出来,而不是执行它。
document.write(‘f();’);
document.write(‘function f(){’);
document.write(’ alert(1);’);
document.write(’);’);
提示:使用document.write()方法输出脚本并执行存在一定的风险,因为不同JavaScript引擎对其执行顺序不同,同时不同浏览器在解析时也会出现各种Bug。
2.5 浏览器与JavaScript
Web浏览器一般包括两部分:Shell和内核。Shell是指浏览器的外壳,如菜单、工具栏等,主要提供用户界面操作、参数设置等,它是调用内核来实现各种功能的UI;内核是浏览器的核心,是基于标记语言显示内容的程序或模块。
目前主流浏览器包括IE、FireFox、Opera、Safari、Chrome。
2.5.1 浏览器内核
浏览器内核可以分为两部分:渲染引擎和JavaScript引擎。它们负责取得网页内容(HTML、XML、图像等)、整理信息(如加入CSS等),以及计算网页的显示方式,然后输出显示。JavaScript引擎负责解析JavaScript脚本,执行JavaScript代码实现网页的动态效果。
常见的浏览器内核包括4种:Trident、Gecko、Presto、Webkit。
Trident又称MSHTML,是微软开发的渲染引擎,包含JavaScript引擎JScript,JScript已经深入Windows系统,如Windows Media Play、Windows Explorer、Outlook Express等都使用它。
Gecko是开源的渲染引擎,包括JavaScript引擎SpiderMonkey(Rhino)。主要使用者为Firefox。
Webkit是苹果公司基于KHTML开发的。它包括Webcore和JavaScriptCore(SquirrelFish、V8)两个引擎。主要使用者有Safari和Chrome。
Presto是由Opera公司开发的,用于Opera的渲染引擎。Macromedia Dreamweaver (MX版本及以上)和Adobe Creative Suite 2也使用了Presto的内核。
主流浏览器所使用的内核分类如下。
☑ Trident内核:IE、MaxThon、TT、The World、360、搜狗浏览器等。
☑ Gecko内核:Netscape 6及以上版本、Firefox、MozillaSuite/SeaMonkey等。
☑ Presto内核:Opera 7及以上。
☑ Webkit内核:Safari和Chrome等。
2.5.2 浏览器错误报告
浏览器都具有某种JavaScript错误报告机制,但在默认情况下,都会隐藏此类信息,在基于浏览器编写JavaScript脚本时,用户应该启用浏览器的JavaScript报告功能,以便及时收集错误信息。
1.IE
在IE中可以通过设置让错误对话框一发生错误就显示出来。为此,要打开“工具”菜单中的“Internet选项”对话框,切换到“高级”选项卡,选中“显示每个脚本错误的通知”复选框,如图2-4所示。单击“确定”按钮保存设置。
图2-4 设置IE选项
保存了设置之后,就会变成一有错误发生随即自动显示出来。另外,如果启用了脚本调试功能(默认是禁用的),那么在发生错误时,不仅会显示错误通知,而且还会看到另一个对话框,询问是否调试错误。要启动脚本调试功能,也可以在“高级”选项卡中取消选中“禁用脚本调试”复选框。
2.Firefox
在默认情况下,Firefox在JavaScript发生错误时不会通过浏览器界面给出提示。但它会在后台将错误记录到错误控制台中。单击“工具”菜单中的“错误控制台”可以显示错误控制台,如图2-5所示。错误控制台中实际上还包含与JavaScript、CSS和HTML相关的警告和信息,可以通过筛选找到错误。
图2-5 Firefox错误控制台
在发生JavaScript错误时,Firefox会将其记录为一个错误,包括错误消息、引发错误的URL和错误所在的行号等信息。单击文件名即可以只读方式打开发生错误的脚本,发生错误的代码行会突出显示。
目前,最流行的Firefox插件Firebug已经成为开发人员必备的JavaScript纠错工具。在有JavaScript错误发生时,Firebug图标会显示错误的数量,单击可以打开Firebug控制台,其中显示有错误消息、错误所在的代码行(不包含上下文)、错误所在的URL以及行号,如图2-6所示。
图2-6 Firebug错误控制台
在Firebug中单击导致错误的代码行,将在一个新Firebug视图中打开整个脚本,该代码行在其中突出显示。
除了显示错误之外,Firebug还有更多的用处。实际上,它还是针对Firefox的成熟的调试环境,为调试JavaScript、CSS、DOM和网络连接错误提供了诸多功能。
3.Safari
Windows和Mac OS平台的Safari在默认情况下都会隐藏全部JavaScript错误。为了访问到这些信息,必须启用“开发”菜单,单击“偏好设置”,然后在“高级”选项卡中选中“在菜单栏中显示‘开发’菜单”命令。启用此项设置之后,就会在Safari的菜单栏中看到一个“开发”菜单,如图2-7所示。
图2-7 启动Safari开发菜单
“开发”菜单中提供了一些与调试有关的选项,还有一些选项可以影响当前加载的页面。单击“显示错误控制台”选项,将会看到一组JavaScript及其他错误,控制台中显示着错误消息、错误的URL及错误的行号,如图2-8所示。单击控制台中的错误消息,就可以打开导致错误的源代码。除了被输出到控制台之外,JavaScript错误不会影响Safari窗口的外观。
图2-8 显示错误控制台
4.Opera
Opera在默认情况下也会隐藏JavaScript错误,所有错误都会被记录到错误控制台中。要打开错误控制台,需要选择“开发者工具”菜单,单击“Web检查器”命令,打开“Web检查器”,然后选择Console选项。与Firefox一样,Opera的错误控制台中也包含了除JavaScript错误之外的很多来源,如HTML、CSS、XML、XSLT等的错误和报告信息。要分类查看不同来源的消息,可以使用左下角的下拉选择框,如图2-9所示。
图2-9 错误控制台
错误消息中显示着导致错误的URL和错误所在的线程。有时还会有栈跟踪信息。除了错误控制台中显示的信息之外,没有其他途径可以获得更多信息。
5.Chrome
与Safari和Opera一样,Chrome在默认情况下也会隐藏JavaScript错误。所有错误都将被记录到JavaScript控制台中。要查看错误消息,选择“工具”菜单中的“JavaScript控制台”命令即可,如图2-10所示。
图2-10 JavaScript控制台
打开窗口中包含着有关页面的信息和JavaScript控制台。控制台中显示着错误消息、错误的URL和错误的行号。单击JavaScript控制台中的错误,就可以定位到导致错误的源代码行。
2.6 JavaScript错误处理
JavaScript属于动态语言,一直没有固定的开发工具,难于调试。当脚本出错时,浏览器通常会给出类似于object expected(缺少对象)这样的消息,没有上下文信息,让人摸不着头脑。ECMAScript第3版致力于解决这个问题,引入try-catch和throw语句以及一些错误类型,适当处理错误。同时,现在主流浏览器中也出现了一些JavaScript调试程序和工具,当有了语言特性和工具支持之后,用户就能够轻松实现错误处理,并且能够找到错误的根源。
2.6.1 使用try-catch
ECMA-262第3版引入了try-catch语句,作为JavaScript处理异常的一种标准方式。基本语法如下:
上面语法与Java中的try-catch语句是完全相同的。
【示例1】用户应把所有可能会抛出错误的代码都放在try语句块中,而把那些用于错误处理的代码放在catch块中。
如果try块中的任何代码发生了错误,就会立即退出代码执行过程,然后执行catch块。此时,catch块会接收到一个包含错误信息的对象。与在其他语言中不同的是,即使不使用这个错误对象,也要给它起个名字。
【示例2】错误对象中包含的实际信息会因浏览器而异,但都有一个保存着错误消息的message属性。ECMA-262还规定了一个保存错误类型的name属性,当前所有浏览器都支持这个属性(Opera 9之前的版本不支持这个属性)。因此,在发生错误时,就可以像下面这样实事求是地显示浏览器给出的消息。
这个例子在向用户显示错误消息时,使用了错误对象的message属性。
提示:message属性是唯一一个能够保证所有浏览器都支持的属性。除此之外,IE、Firefox、Safari、Chrome以及Opera都为事件对象添加了其他相关信息。
例如,IE添加了与message属性完全相同的description属性,还添加了保存着内部错误数量的number属性;Firefox添加了fileName、lineNumber和stack(包含栈跟踪信息)属性;Safari添加了line(表示行号)、sourceId(表示内部错误代码)和sourceURL属性。当然,在跨浏览器编程时,建议只使用message属性。
【拓展】当try-catch语句中发生错误时,浏览器会认为错误已经被处理了,因而不会报告错误。对于那些不要求用户懂技术,也不需要用户理解错误的Web应用程序。这应该说是个理想的结果。不过try-catch能够让我们实现自己的错误处理机制。
使用try-catch最适合处理那些无法控制的错误。假设在使用一个大型JavaScript库中的函数,该函数可能会有意无意地抛出一些错误。由于我们不能修改这个库的源代码,所以大可将对该函数的调用放在try-catch语句当中,万一有什么错误发生,也好恰当地处理它们。
在明明白白地知道自己的代码会发生错误时,再使用try-catch语句就不太合适了。例如,如果传递给函数的参数是字符串而非数值,就会造成函数出错,那么就应该先检查参数的类型,然后再决定如何去做。在这种情况下,不使用try-catch语句。
2.6.2 使用finally
finally子句在try-catch语句中是可选的,但finally子句已经使用,其代码无论如何都会执行。无论try或catch语句块中包含什么代码—甚至return语句,都不会阻止finally子句的执行。
【示例】只要代码中包含finally子句,那么无论try还是catch语句块中的return语句都将被忽略。因此,在使用finally子句之前,一定要非常清楚想让代码怎么样。看下面这个函数。
这个函数在try-catch语句的每一部分都放了一条return语句。表面上看,调用这个函数会返回2,因为返回2的return语句位于try语句块中,而执行该语句又不会出错。可是,由于最后还有一个finally子句,结果就会导致该return语句被忽略,也就是说,调用这个函数只能返回0。如果把finally子句去掉,这个函数将返回2。
提示:如果提供finally子句,则catch子句就成了可选的,IE 7及更早版本中有一个Bug:除非有catch子句,否则finally中的代码永远不会执行。如果考虑兼容IE早期版本,应提供一个catch子句,哪怕里面什么都不写,IE 8修复了这个Bug。
2.6.3 错误类型
执行代码期间可能会发生的错误有多种类型。每种错误都有对应的错误类型,而当错误发生时,就会抛出相应类型的错误对象。ECMA-262定义了下列7种错误类型:
☑ Error
☑ EvalError
☑ RangeError
☑ ReferenceError
☑ SyntaxError
☑ TypeError
☑ URIError
其中Error是基类型,其他错误类型都继承自该类型。因此,所有错误类型共享了一组相同的属性,错误对象中的方法全是默认的对象方法。Error类型的错误很少见,如果有也是浏览器抛出的,这个基类型的主要目的是供开发人员抛出自定义错误。
EvalError类型的错误会在使用eval()函数而发生异常时被抛出。
【示例1】如果没有把eval()当成函数调用,就会抛出该类型错误。
在实践中,浏览器不一定会在应该抛出错误时就抛出EvalError。例如,Firefox 4+和IE 8对第一种情况会抛出TypeError,而第二种情况会成功执行,不发生错误。因此,在实际开发中极少会这样使用eval(),所以遇到这种错误类型的可能性极小。
RangeError类型的错误会在数值超出相应范围时触发。JavaScript中经常会出现这种范围错误。
【示例2】在定义数组时,如果指定了数组不支持的项数,如-20或Number.MAX_VALUE,就会触发这种错误。
在找不到对象的情况下,会发生ReferenceError。
【示例3】在访问不存在的变量时,就会发生这种错误。
var obj=x; //在x并未声明的情况下抛出ReferenceErro
SyntaxError表示语法类型错误,当把语法错误的JavaScript字符串传入eval()函数时,就会导致此类错误。例如:
eval(“a ++ b”) //抛出SyntaxError
如果语法错误的代码出现在eval()函数之外,则不太可能使用SyntaxError,因为此时的语法错误会导致JavaScript代码立即停止执行。
TypeError类型在JavaScript中会经常用到,在变量中保存着意外的类型时,或者在访问不存在的方法时,都会导致这种错误。错误的原因虽然多种多样,但归根结底还是由于在执行特定于类型的操作时,变量的类型并不符合要求所致。
【示例4】下面来看几个例子。
最常发生类型错误的情况,就是传递给函数的参数事先未经检查,结果传入类型与预期类型不相符。
在使用encodeURL()或decodeURL()时,如果URL格式不正确,就会导致URLError错误。这种错误也很少见,因为这两个函数的容错性非常高。
利用不同的错误类型,可以获悉更多有关异常的信息,从而有助于对错误作出恰当的处理。
【应用】如果想知道错误的类型,可以按下面这样在try-catch语句的catch语句中使用instanceof操作符。
在跨浏览器编程中,检查错误类型是确定处理方式的最简便途径,包含在message属性中的错误消息会因浏览器而异。
2.6.4 抛出错误
与try-catch语句相配的还有一个throw操作符,用于随时抛出自定义错误。抛出错误时,必须要给throw操作符指定一个值,这个值是什么类型没有要求。
【示例1】下列代码都是有效的。
throw 1;
throw"hi"
throw true;
throw {name:“js”};
在遇到throw操作符时,代码会立即停止执行。仅当有try-catch语句捕获到被抛出的值时,代码才会继续执行。
通过使用某种内置错误类型,可以更真实地模拟浏览器错误。每种错误类型的构造函数接收一个参数,即实际的错误消息。
【示例2】下面是一个例子。
throw new Error(“抛出错误”);
这行代码抛出了一个通用错误,带有一条自定义错误消息。浏览器会像处理自己生成的错误一样,来处理这行代码抛出的错误。即浏览器会以常规方式报告这一错误,并且会显示这里的自定义错误消息。
【示例3】下面代码使用其他错误类型,也可以模拟出类似的浏览器错误。
throw new SyntaxError(“SyntaxError”);
throw new TypeError(“TypeError”);
throw new RangeError(“RangeError”);
throw new EvalError(“EvalError”);
throw new URIError(“URIError”);
throw new ReferenceError(“ReferenceError”);
在创建定义错误消息时,最常用的错误类型是Error、RangeError、ReferenceError和TypeError。
【应用】利用原型链还可以通过继承Error来创建自定义错误类型。此时,需要为新创建的错误类型指定name和message属性。
浏览器对待继承自Error的自定义错误类型,就像对待其他错误类型一样。如果要捕获自己抛出的错误并且把它与浏览器错误区别对待,创建自定义错误是很有用的。
提示:IE只有在抛出Error对象时才会显示自定义错误消息。对于其他类型,它都无一例外地显示exception thrown and not caught(抛出了异常,且未被捕获)。
2.6.5 案例:设计抛出错误时机
针对函数执行失败给出更多信息,抛出自定义错误是一种很方便的方式。应该在出现某种特定的已知错误条件,导致函数无法正常执行时抛出错误。
【示例1】下面函数会在参数不是数组的情况下失败。
如果执行这个函数时传给它一个字符串参数,那么调用sort()就会失败。对此,不同浏览器会给出不同的错误消息,但都不是特别明确。
【示例2】在处理上面示例中的这个函数时,通过调试处理这些错误消息没有什么困难。但是,在面对包含数千行JavaScript代码的复杂的Web应用程序时,要想查找错误来源就没有那么容易了。在这种情况下,带有适当信息的自定义错误能够显著提升代码的可维护性。
重写函数后,如果values参数不是数组,就会抛出一个错误。错误消息中包含了函数的名称,以及为什么会发生错误的明确描述。如果一个复杂的Web应用程序发生了这个错误,那么查找问题的根源也就容易多了。
提示:在开发JavaScript代码的过程中,重点关注函数和可能导致函数执行失败的因素。良好的错误处理机制应该可以确保代码中只发生自己抛出的错误。
【拓展】何时该抛出错误,而何时该使用try-catch来捕获错误信息?
一般来说,应用程序架构的较低层次中经常会抛出错误,但这个层次并不会影响当前执行的代码,因而错误通常得不到真正的处理。如果打算编写一个要在很多应用程序中使用的JavaScript库,甚至只编写一个可能会在应用程序内部多个地方使用的辅助函数,建议用户在抛出错误时提供详尽的信息。然后,即可在应用程序中捕获并适当地处理这些错误。
在程序中,应该捕获那些确切地知道该如何处理的错误。捕获错误的目的在于避免浏览器以默认方式处理它们,而抛出错误的目的在于提供错误发生具体原因的消息。
2.6.6 错误事件
任何没有通过try-catch处理的错误都会触发window对象的error事件。这个事件是浏览器最早支持的事件之一,IE、Firefox和Chrome为保持向后兼容,并没有对这个事件做任何修改,Opera和Safari不支持error事件。
在任何Web浏览器中,onerror事件处理程序都不会创建event对象,但它可以接收3个参数:错误消息、错误所在的URL和行号。多数情况下,只有错误消息有用,因为URL只是给出了文档的位置,而行号所指的代码行既可能出自嵌入的JavaScript代码,也可能出自外部的文件。
要指定onerror事件处理程序,必须使用如下所示的DOM 0级技术,它没有遵循DOM 2级事件的标准格式。
只要发生错误,无论是不是浏览器生成的,都会触发error事件,并执行这个事件处理程序。然后,浏览器默认的机制发挥作用,像往常一样显示出错误消息。
【示例1】通过下面方法在事件处理程序中返回false,可以阻止浏览器报告错误的默认行为。
通过返回false,这个函数实际上就充当了整个文档中的try-catch语句,可以捕获所有无代码处理的运行时错误。这个事件处理程序是避免浏览器报告错误的最后一道防线,理想情况下,只要可能就不应该使用它。只要能够适当地使用try-catch语句,就不会有错误交给浏览器,也就不会触发error事件。
提示:不同浏览器在使用error事件处理错误时的方式有明显不同。在IE中,即使发生error事件,代码仍然会正常执行,所有变量和数据都将得到保留,因此能在onerror事件处理程序中访问它们。但在Firefox中,常规代码会停止执行,事件发生之前的所有变量和数据都将被销毁,因此几乎就无法判断错误了。
【拓展】图像也支持error事件。只要图像的src特性中的URL不能返回可以被识别的图像格式,就会触发error事件。此时的error事件遵循DOM格式,会返回一个以图像为目标的event对象。
在这个例子中,当加载图像失败时就会显示一个警告框。注意,发生error事件时,图像下载过程已经结束,也就是说不能再重新下载了。
2.6.7 错误类型
错误处理的核心是首先要知道代码里会发生什么错误。由于JavaScript是松散类型的,而且也不会验证函数的参数,因此错误只会在代码运行期间出现。一般来说,需要关注3种错误:
☑ 类型转换错误。
☑ 数据类型错误。
☑ 通信错误。
以上错误分别会在特定的模式下或者没有对值进行足够的检查的情况下发生。
任何错误处理策略中的重点就是确定错误是否致命。对于非致命错误,可以根据下列一个或多个条件来确定:
☑ 不影响用户的主要任务。
☑ 只影响页面的一部分。
☑ 可以恢复。
☑ 重复相同操作可以消除错误。
本质上,非致命错误并不是需要关注的问题。没有必要因为发生了非致命错误而对用户给出提示,可以把页面中受到影响的区域替换掉,例如替换成说明相应功能无法使用的消息。但是,如果因此打断用户,那确实没有必要。
致命错误,可以通过以下一个或多个条件来确定:
☑ 应用程序根本无法继续运行。
☑ 错误明显影响到了用户的主要操作。
☑ 会导致其他连带错误。
要想采取适当的措施,必须要知道JavaScript在什么情况下会发生致命错误。在发生致命错误时,应该立即给用户发送一条消息,告诉无法再继续执行了。假如必须刷新页面才能让应用程序正常运行,就必须通知用户,同时给用户提供一个点击即可刷新页面的按钮。
区分非致命错误和致命错误的主要依据,就是看它们对用户的影响。设计良好的代码,可以做到应用程序某一部分发生错误不会影响另一个毫不相干的部分。例如,My Yahoo!(https://my.yahoo.com/)的个性化主页包含了很多互不依赖的模块。如果每个模块都需要通过JavaScript调用来初始化,那么用户可能会看到类似下面这样的代码:
表面上看,这些代码没什么问题,依次对每个模块调用init()方法。问题在于,任何模块的init()方法如果出错,都会导致数组中后续的所有模块无法再进行初始化。从逻辑上说,这样编写代码没有什么意义。毕竟每个模块相互之间没有依赖关系,各自实现不同功能。可能会导致致命错误的原因是代码的结构。不过使用如下代码就可以把所有模块的错误变成非致命的:
通过在for循环中添加try-catch语句,任何模块初始化时出错,都不会影响其他模块的初始化。在以上重写的代码中,如果有错误发生,相应的错误将会得到独立的处理,并不会影响到用户的体验。
2.6.8 案例:记录错误
在开发Web应用程序过程中,应该集中保存错误日志,以便查找重要错误的原因。例如,数据库和服务器错误都会定期写入日志,而且会按照常用API进行分类。在复杂的Web应用程序中,用户也应该把JavaScript错误回写到服务器,并标明它们来自前端。把前后端的错误集中起来,能够极大地方便对数据的分析。
要建立这样一种JavaScript错误记录系统,首先需要在服务器上创建一个页面(或者一个服务器入口点),用于处理错误数据。这个页面的作用无非就是从查询字符串中取得数据,然后再将数据写入错误日志中。这个页面可能会使用如下所示的函数:
在上面代码中,logError()函数接收两个参数:表示严重程度的数值或字符串,以及错误消息。其中,使用了Image对象来发送请求,这样做非常灵活,主要表现在如下几方面。
☑ 所有浏览器都支持Image对象,包括那些不支持XMLHttpRequest对象的浏览器。
☑ 可以避免跨域限制。通常都是一台服务器要负责处理多台服务器的错误,而这种情况下使用XMLHttpRequest是不行的。
☑ 在记录错误的过程中出现问题的概率比较低。大多数Ajax通信都是由JavaScript库提供的包装函数来处理的,如果库代码本身有问题,而依赖该库记录错误,可想而知,错误消息是不可能得到记录的。
只要是使用try-catch语句,就应该把相应错误记录到日志中。
在上面代码中,一旦模块初始化失败,就会调用logError()函数,其中第一个参数表示错误的性质,第二个参数是真正的JavaScript错误消息。记录到服务器中的错误消息应该尽可能多地带有上下文信息,以便鉴别导致错误的真正原因。
2.7 JavaScript代码调试
在JavaScript开发初期,开发人员常在要调试的代码中随处插入alert()函数。但这种做法一方面比较麻烦(调试之后还需要清理),另一方面还可能引入新问题,如果把alert()函数遗留在产品代码后会带来很多麻烦。如今,已经有了很多更好的调试工具,因此就不再建议在调试中使用alert()了。
2.7.1 使用控制台
IE、Firefox、Opera、Chrome和Safari都有JavaScript控制台,可以用来查看JavaScript错误。而且,在这些浏览器中都可以通过代码向控制台输出消息。对Firefox而言,需要安装Firebug (http://getfirebug.com/),因为Firefox要使用Firebug的控制台。对IE、Firefox、Chrome和Safari来说,都可以通过console对象向JavaScript控制台中写入消息,该对象包含下列方法。
☑ error(message):将错误消息记录到控制台。
☑ info(message):将信息性消息记录到控制台。
☑ log(message):将一般消息记录到控制台。
☑ warn(message):将警告消息记录到控制台。
在IE、Firebug、Chrome和Safari中,用来记录消息的方法不同,控制台中显示的错误消息也不一样。错误消息带有红色图标,而警告消息带有黄色图标。
【示例1】以下函数展示了使用控制台输出消息的一个示例。
在浏览器中执行上面代码,则可以在控制台中看到输出信息,如图2-11所示。
图2-11 在IE控制台中写入信息
【示例2】不存在一种跨浏览器向JavaScript控制台写入消息的机制,但下面的函数可以作为统一的接口。
这个log()函数检测了哪个JavaScript控制台接口可用,然后使用相应的接口。可以在任何浏览器中安全地使用这个函数,不会导致任何错误,例如:
向JavaScript控制台中写入消息可以辅助调试代码,但在发布应用程序时,还必须要移除所有消息。在部署应用程序时,可以通过手工或特定的代码处理步骤来自动完成清理工作。
提示:记录消息要比使用alert()函数更可取,因为警告框会阻断程序的执行,而在测定异步处理对时间的影响时,使用警告框会影响结果。
2.7.2 显示错误信息
另一种输出调试消息的方式,就是在页面中开辟一小块区域,用以显示消息。这个区域常是一个元素,而该元素可以总是出现在页面中,但仅用于调试目的,也可以是一个根据需要动态创建的元素。
【示例】针对2.7.1节的log()函数,可以将log()函数修改为如下所示:
这个修改后的log()函数首先检测是否已经存在调试元素,如果没有则会新创建一个
然后,在页面中进行测试,则效果如图2-12所示。
图2-12 在IE中显示信息
提示:与把错误消息记录到控制台相似,把错误消息输出到页面的代码也要在发布前清除。
2.7.3 抛出错误
如前所述,抛出错误也是一种调试代码的好办法。如果错误消息很具体,基本上就可以把它当作确定错误来源的依据。但这种错误消息必须能够明确给出导致错误的原因,才能省去其他调试操作。
【示例】看下面的函数。
这个简单的函数计算两个数的除法,但如果有一个参数不是数值,它会返回NaN。类似这样简单的计算如果返回NaN,就会在Web应用程序中导致问题。对此,可以在计算之前,先检测每个参数是否都是数值。
在此,如果有一个参数不是数值,就会抛出错误。错误消息中包含了函数的名字,以及导致错误的真正原因。浏览器只要报告了这个错误消息,我们就可以立即知道错误来源及问题的性质。相对来说,这种具体的错误消息要比那些泛泛的浏览器错误消息更有用。
对于大型应用程序来说,自定义的错误通常都使用assert()函数抛出。这个函数接收两个参数,一个是求值结果应该为true的条件,另一个是条件为false时要抛出的错误。以下就是一个非常基本的assert()函数。
可以用这个assert()函数代替某些函数中需要调试的if语句,以便输出错误消息。下面是使用这个函数的例子。
可见,使用assert()函数可以减少抛出错误所需的代码量,而且也比前面的代码更容易看懂。
2.7.4 IE错误
多年以来,IE一直都是最难于调试JavaScript错误的浏览器。IE给出的错误消息一般很短又语意不详,而且上下文信息也很少,有时甚至一点都没有。但作为用户最多的浏览器,如何看懂给出的错误也是最受关注的。下面简单介绍在IE中常见的难于调试的JavaScript错误。
1.操作终止
在IE 8之前的版本中,存在一个相对于其他浏览器而言,最难于调试的错误:操作终止(operation aborted)。在修改尚未加载完成的页面时,就会发生操作终止错误。发生错误时,会出现一个模态对话框,提示“操作终止。”,单击“确定”(OK)按钮,则卸载整个页面,继而显示一张空白屏幕,此时要进行调试非常困难。
【示例1】下面的示例试图在页面未完全加载时就对文档结构进行操作,会引发错误。
在这个例子中存在的问题:JavaScript代码在页面尚未加载完毕时就要修改document.body,而且<script>元素还不是元素的直接子元素。准确一点说,当<script>节点被包含在某个元素中,而且JavaScript代码又要用appendChild()、innerHTML,或其他DOM方法修改该元素的父元素或祖先元素时,将会发生操作终止错误,因为它只能修改已经加载完毕的元素。
要避免这个问题,可以等到目标元素加载完毕后再对它进行操作,或者使用其他操作方法。例如,为document.body添加一个绝对定位在页面上的覆盖层,就是一种非常常见的操作。通常,开发人员都是使用appendChild()方法来添加这个元素的,但使用insertBefore()方法也很容易。因此,只要修改前面例子中的一行代码,就可以避免操作终止错误。
在这个例子中,新的
【示例2】除了改变方法之外,还可以把<script>元素从包含元素中移来,直接作为的子元素。
这一次也不会发生错误,因为脚本修改的是它的直接父元素,而不再是间接的祖先元素。
在同样的情况下,IE 8不再抛出操作终止错误,而是抛出常规的JavaScript错误。不过,虽然浏览器抛出的错误不同,但解决方案仍然是一样的。
2.无效字符
根据语法,JavaScript文件必须只包含特定的字符。在JavaScript文件中存在无效字符时,IE会抛出无效字符(Invalid Character)错误。所谓无效字符,就是JavaScript语法中未定义的字符。
例如,有一个很像减号但却由Unicode值8211表示的字符(\u2013),就不能用作常规的减号(ASCII编码为45),因为JavaScript语法中没有定义该字符。这个字符通常是在Word文档中自动插入的。如果JavaScript代码是从Word文档中复制到文本编辑器中,然后又在IE中运行的,那么就可能会遇到无效字符错误。其他浏览器对无效字符做出的反应与IE类似,Firefox会抛出非法字符(Illegal Character)错误,Safari会报告发生了语法错误,而Opera则会报告发生了ReferenceError(引用错误),因为它会将无效字符解释为未定义的标识符。
3.未找到成员
IE中所有DOM对象都是COM对象,而非原生JavaScript对象的形式实现的。这会导致一些与垃圾收集相关的非常奇怪的行为。IE中的未找到成员(Member not found)错误,就是由于垃圾收集例程配合错误所直接导致的。
具体来说,如果在对象被销毁之后,又给该对象赋值,将会导致未找到成员错误。而导致这个错误的一定是COM对象。发生这个错误的最常见情形是使用event对象时。IE中的event对象是window的属性,该对象在事件发生时创建,在最后一个事件处理程序执行完毕后销毁。
【示例3】假设在一个闭包中使用了event对象,而该闭包不会立即执行,那么在将来调用它并给event的属性赋值时,就会导致未找到成员错误。
在这段代码中,将一个单击事件处理程序指定给了文档。在事件处理程序中.window.event被保存在event变量中。然后,传入setTimeout()中的闭包里又包含了event变量。当单击事件处理程序执行完毕后,event对象就会被销毁,因而闭包中引用对象的成员就成了不存在的。因此,在闭包中给returnValue赋值就会导致未找到成员错误。
4.未知运行时错误
当使用innerHTML或outerHTML以下列方式指定HTML时,就会发生未知运行时错误(Unknown runtime error):一是把块元素插入到行内元素时,二是访问表格任意部分(、等)的任意属性时。
【示例4】从技术角度说,标签不能包含
span.innerHTML =“Hi”; //这里span包含元素
在遇到把块级元素插入到不恰当位置的情况时,其他浏览器会尝试纠正并隐藏错误,而IE会提示错误。
5.语法错误
一般情况下,只要IE报告发生了语法错误(Syntax Error),都可以很快找到错误的原因,但还有一种原因不是十分明显的情况需要格外注意。
如果用户引用了外部的JavaScript文件,而该文件最终并没有返回JavaScript代码,IE也会抛出语法错误。例如,<script>元素的src特性指向了一个HTML文件,就会导致语法错误。报告语法错误的位置时,通常都会说该错误位于脚本第一行的第一个字符处。Opera和Safari也会报告语法错误,但它们会给出导致问题的外部文件的信息,IE就不会给出这个信息,因此就需要自己重复检查一遍引用的外部JavaScript文件,但Firefox会忽略这种解析错误。
6.系统无法找到指定资源
在使用JavaScript请求某个资源URL,而该URL的长度超过了IE对URL最长不能超过2083个字符的限制时,就会发生这个错误。IE不仅限制JavaScript中使用的URL的长度,而且也限制用户在浏览器自身中使用的URL长度(其他浏览器对URL的限制没有这么严格),IE对URL路径还有一个不能超过2048个字符的限制。
【示例5】下面的代码将会导致错误。
在这个例子中,XMLHttpRequest对象试图向一个超出最大长度限制的URL发送请求。在调用open()方法时,就会发生错误。避免这个问题的办法,无非就是通过给查询字符串参数起更短的名字,或者减少不必要的数据,来缩短查询字符串的长度。另外,还可以把请求方法改为POST,通过请求体而不是查询字符串来发送数据。