整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript异常错误处理指南

JavaScript异常错误处理指南



前端的 JavaScript 开发中,发现开发者对于错误异常的处理普遍都比较简单粗暴,如果应用程序中缺少有效的错误处理和容错机制,代码的健壮性就无从谈起。本文整理出了一些常见的错误异常处理的场景,旨在为前端的 JavaScript 错误异常处理提供一些基础的指导。


Error 对象

先来简单介绍一下 JavaScript 中的 Error 对象,通常 Error 对象由重要的两部分组成,包含了 error.message 错误信息和 error.stack 错误追溯栈。

产生一个错误很简单,比如在 foo.js 中直接调用一个不存在的 callback 函数。

// foo.js
function foo () {
    callback();
}

foo();

此时通过 Chrome 浏览器的控制台会展示如下的信息。

Uncaught ReferenceError: callback is not defined
    at foo (foo.js:2)
    at foo.js:5

其中 Uncaught ReferenceError: callback is not defined 就是 error.message 错误信息,而剩下的 at xxx 就是具体的错误追溯栈,在 Chrome 的控制台中,对错误的展示进行了优化。

如果我们通过 window.onerror 来捕获到该错误后将 Error 对象直接输出到页面中会展示出更原始的数据。

<!-- 展示错误的容器 -->
<textarea id="error"></textarea>

// 输出错误
window.onerror=function (msg, url, line, col, err) {
    document.getElementById('error').textContent=err.message + '\n\n' + err.stack;
};

原始的错误数据中会展示出错误追溯栈中的 Source URL。

callback is not defined

ReferenceError: callback is not defined
    at foo (http://example.com/js-error/foo.js:2:5)
    at http://example.com/js-error/foo.js:5:1

有了错误追溯栈,就能通过发生错误的文件 Source URL 和错误在代码中的具体位置来快速定位到错误。

看起来好像很简单,但实际的开发中如何有效的捕获错误,如何有效的抛出错误都有一些需要注意的点,下面逐个的来讲解。


window.onerror

前端在捕获错误时都会通过绑定 window.onerror 事件来捕获全局的 JavaScript 执行错误,标准的浏览器在响应该事件时会依次提供 5 个参数。

window.onerror=function(message, source, lineno, colno, error) { ... }
  1. message 错误信息
  2. source 错误发生时的页面 URL
  3. lineno 错误发生时的 JS 文件行数
  4. colno 错误发生时的 JS 文件列数
  5. error 错误发生时抛出的标准 Error 对象

使用 window.addEventListener 也能绑定 error 事件,但是该事件函数的参数是一个 ErrorEvent 对象。

绑定 window.onerror 事件时,事件处理函数的第 5 个参数在低版本浏览中或 JS 资源跨域场景下可能不是 Error 对象。


在 Chrome 浏览器中如果页面加载的 JS 资源文件中存在跨域的 script 标签,在发生错误时会提示 Script error 而缺乏错误追溯栈。

window.onerror 在响应跨域 JavaScript 错误时缺乏错误追溯栈时的 arguments 对象如下:

[
    'Script error.',
    '',
    0,
    0,
    null
]

为了正常的捕获到跨域 JS 资源文件的错误,需要具备两个条件: 1. 为 JS 资源文件增加 CORS 响应头。 2. 通过 script 引用该 JS 文件时增加 crossorigin="anonymous" 的属性,如果是动态加载的 JS,可以写作 script.crossOrigin=true 。

window.onerror 能捕获一些全局的 JavaScript 错误,但还有不少场景在全局是捕获不到的。


try/catch

window.onerror 能捕获全局场景下的错误,如果已知一些程序的场景中可能会出现错误,这个时候一般会使用 try/catch 来进行捕获。

但是在使用 try/catch 块时无法捕获异步错误,例如块中使用了 setTimeout 。

try {
    setTimeout(function () {
        callTimeout();  // callTimeout 未定义,会抛错
    }, 1000);
}
catch (err) {
    console.log('catch the error', err); // 不会被执行
}

try/catch 在处理 setTimeout 这类异步场景时是无效的,执行时仍会抛错,catch 中的代码不会被执行。

虽然在 try/catch 中没有捕获到,此时如果有绑定 window.onerror 则会被全局捕获。

由此可见, try/catch 应该是只能捕获 JS Event Loop 中同步的任务。

如果想正确的捕获 setTimeout 中的错误,需要将 try/catch 块写到 setTimeout 的函数中。

setTimeout(function () {
    try {
        callTimeout(); // callTimeout 未定义,不会抛错
    }
    catch (err) {
        console.log('catch the error', err); // 将会被执行
    }
}, 1000);

Promise

Promise 有自己的错误处理机制,通常 Promise 函数中的错误无法被全局捕获。

var promise=new Promise(executor);
promise.then(onFulfilled, onRejected);

比较容易遗漏错误处理的地方有 executor 和 onFulfilled ,在这些函数中如果发生错误都不能被全局捕获。

正确的捕获 Promise 的错误,应该使用 Promise.prototype.catch 方法,意外的错误和使用 reject 主动捕获的错误都会触发 catch 方法。


catch 方法中通常会接收到一个 Error 对象,但是当调用 reject 函数时传入的是一个非 Error 对象时,catch 方法也会接收到一个非 Error 对象,这里的 reject 和 throw 的表现是一样的,所以在使用 reject 时,最好是传入一个 Error 对象。

reject(
    new Error('this is reject message')
);

值得注意的是,如果 Promise 的 executor 中存在 setTimeout 语句时, setTimeout 的报错会被全局捕获。


Async Function

Async Function 和 Promise 一样,发生错误不会被全局的 window.onerror 捕获,所以在使用时如果有报错,需要手动增加 try/catch 语句。

匿名函数

匿名函数的使用在 JavaScript 中很常见,但是当出现匿名函数的报错时,在错误追溯栈中会以 anonymous 来标识错误,为了排查错误方便,可以将函数进行命名,或者使用函数的 displayName 属性。

函数如果有 displayName 属性,在错误栈中会展示该属性值,如果用于命名重要的业务逻辑属性,将有效帮助排查错误。


throw error

上面说了很多错误捕获的注意点,如果要主动的抛错,都会使用 throw 来抛错,常见的几种抛错方法如下:

throw new Error('Problem description.')  // 方法 1
throw Error('Problem description.')      // 方法 2
throw 'Problem description.'             // 方法 3
throw null                               // 方法 4

其中方法 1 和方法 2 的效果一样,浏览器都能正确的展示错误追溯栈。方法 3 和方法 4 不推荐,虽然能抛错,但是在抛错的时候不能展示错误追溯栈。

try/catch 和 throw ,一个用来捕获错误,一个用来抛出错误,如果两个结合起来用通常等于脱了裤子放屁多此一举,唯一有点用的是可以对错误信息进行再加工。

可以在 Chrome 控制台中模拟出一个结合使用的实际场景。

try {
    foo();
}
catch (err) {
    err.message='Catch the error: ' + err.message;
    throw Error(err);
}

由于在 catch 块中又抛出了错误,所以该错误没有被捕获到,但此时错误信息经过了二次封装。

Uncaught Error: ReferenceError: Catch the error: foo is not defined

通过对错误信息的二次封装,可以增加一些有利于快速定位错误的额外信息。


原作者:雨夜带刀's Blog

、Javascript的异常处理机制

当javascript代码中出现错误的时候,js引擎就会根据js的调用栈逐级寻找对应的catch,如果没有找到相应的catch handler或catch handler本身又有error或者又抛出新的error,最后就会把这个error的处理交给浏览器,并显示在错误控制台中)显示错误信息给访问者。

二、try/catch/finally

是js提供的异常处理机制,用法如下:

try {
// 这段代码从上往下运行,其中任何一个语句抛出异常该代码块就结束运行}
catch (e) {
// 如果try代码块中抛出了异常,catch代码块中的代码就会被执行。
// e是一个局部变量,用来指向Error对象或者其他抛出的对象
}
finally {
  //无论try中代码是否有异常抛出(甚至是try代码块中有return语句),
       //finally代码块中始终会被执行
}

三、常见错误

3.1 syntaxError

顾名思义,典型的语法错误。

function foo{
}
if{}

Js代码是从上往下依次执行,但是js引擎先解析代码是否语法出错,如果语法都解析不通过,那么显而易见:一行代码也不会执行,从而会在控制台中输出语法报错:


syntaxError错误

3.2 变量未定义

变量未定义(也叫未声明)指的是,当程序中使用了一个未定义的变量则会报错。

如下代码:

var foo=1
var bar=foo + n1

显而易见,n1变量是未定义的就直接使用,从而会在控制台中输出如下错误:


XXX is not defined 变量未定义

3.3 TypeError错误

TypeError错误指的是数据类型未正确使用。

例如一:

var foo=function(){
	console.log('foo')
}
foo='hello world'
foo()

在某些逻辑下,foo本身存储的是函数,但是误把foo赋值了一个字符串或其它不是函数的数据类型,但是foo当作函数来调用了,则会报TypeError错误在控制台中输出:


TypeError,xxx is not a function

例如二:

未正确获取元素,导致得到一个null而不是DOM节点对象后,绑定事件而引发的TypeError错误。

<script>
var oBtn=document.getElementById('btn') 
//因为代码从上往下解析的原因,还未解析到button标签,返回为null。
//null是空对象,不能绑定任何属性,onclick虽然是事件,
//但也是对象中属性的一部分,所以报TypeError错误。
oBtn.onclick=function(){
	console.log('bar')
}
</script>
<button id="btn">foo</button>


cannot set property 'onclick' of null

正确错误是把选择元素的js代码放置html标签之后,也就是紧邻 </body>标签,或放在windo.onload事件中。

<script>
	window.onload=function(){
		var oBtn=document.getElementById('btn') 
    //因为代码从上往下解析的原因,还未解析到button标签,返回为null。
    //null是空对象,不能绑定任何属性,onclick虽然是事件,
    //但也是对象中属性的一部分,所以报TypeError错误。
    oBtn.onclick=function(){
      console.log('bar')
    }
	}
</script>
<button id="btn">foo</button>

3.4 JSON解析错误

首先,我们需要了解JSON是什么 ?

JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。

而它的定义规则和js中字面量声明对象很像,所以很多初学者以为json就是js对象,其实这是错误的。

3.4.1 JSON 是 JS对象表示语法的子集。

  • 数据在名称/值对中
  • 数据由逗号分隔
  • 大括号 {} 保存对象
  • 中括号 [] 保存数组,数组可以包含多个对象

3.4.2 JSON 数据的书写格式是

key : value

名称/值包括字段名称(在双引号中),后面写一个冒号,然后是值:

"name" : "foo"

3.4.3 JSON 值可以是:

  • 数字(整数或浮点数)
  • 字符串(在双引号中)
  • 逻辑值(true 或 false)
  • 数组(在中括号中)
  • 对象(在大括号中)
  • null

前方高能~~~

谈到这里,json数据从哪来呢?

在请求Ajax的时候,会从后台服务器中拿到json数据,往往会把json解析成js对象。则前端工程师会用到JSON.parse方法。有时候前端也会定义JSON数据,如果语法不正确当转成js对象时,则会报错。如下代码:

//var foo='{ name:"bar" }'//name未带双引号
var foo='{ "name":bar }'//bar未带双引号

var f=JSON.parse( foo )


token n in JSON at position

正确的JSON转换js对象的方式如下:

var foo='{ "name":"bar","age":20 }'//20无需带,则理解为数值类型

var f=JSON.parse( foo )
console.log( f ) //{name: "bar", age: 20} ,此时可以正确的把json转换成js对象,
												//通过 点 语法,也就是f.name和f.age访问到具体的数据

以上是JavaScript中常见的错误,后期遇到会不断更新,感谢小伙伴们的踊跃投稿和留言。

内容是《Web前端开发之Javascript视频》的课件,请配合大师哥《Javascript》视频课程学习。

错误处理对于web应用开发至关重要,任何javascript错误都有可能会导致网页无法使用,因此作为开发人员,必须要及时处理有可能出现的错误;

从IE4.0之后,几乎所有的浏览器都包含了一些基本的错误处理功能,但并没有统一,后来,由ECMAscript添加了异常处理机制,也就是try…catch…finally结构以及throw操作;

错误处理的重要性: 好的错误处理技术可以让脚本的开发、调试和部署更加流畅,能对代码进行更好的控制;另外,JS缺乏标准的开发环境;

错误类型:

语法错误(syntax error):也称为解析错误,发生在传统编程语言的编译解释时;发生语法错误时,就会发生阻塞,也就是不能继续执行代码,但只是同一个线程中的代码会受影响,其他线程中的代码不受影响;

// Uncaught SyntaxError: Invalid or unexpected token
// 未捕获的语法错误:无效或意外的标记
document.write("zeronetwork;

运行时错误(Runtime error):也称为exception异常, 其发生在编译期/解释期后,此时,问题并不出现在代码的语法上,而是在尝试完成一个非法的操作;

<input type="button" value="单击" onclick="handleClick()" />
<script>
// Uncaught ReferenceError: openMy is not defined
// 未捕获的引用错误:未定义openMy
function handleClick(){
    openMy();
}
</script>

错误报告:

因为每个浏览器都有自己的内置Javascript解释程序,所以每种浏览器报告错误的方式都不同;有些是弹出错误信息,有些是把信息打印在控制台中;

IE(windows): 默认情况下,会弹出包含错误细节的对话框,并询问是否继续执行页面上的脚本;如果浏览器有调试器(如:Microsoft Script Debugger) ,此对话框会提供一个是调试还是忽略的选项;如果在IE设置中取消了”显示错误”,那么会在页面左下角显示一个黄色的图标;

注:如果JS代码就在HTML里,显示错误行是正确的,如果是外部的JS文件,则行号往往差一行,如第5行则为第4行;

Mozilla(所有平台): 在控制台中打印错误信息,并发出警告;其会报告三种类型的消息:错误、严格警告和消息等的;

Safari (MacOS):是对JavaScript错误和调试的支持最差,默认情况下,它对终端用户不提供任何javascript错误报告;

错误处理:

Javascript提供了两种处理错误的方式:

  • BOM包含一个onerror事件处理函数,该函数通常被绑定在window对象或image对象上;
  • ECMAscript定义了try…catch结构来处理异常;

onerror事件处理函数:

window对象的onerror属性是一个事件处理程序,页面上出现异常时,error事件便在window对象上触发,并把错误消息输出到Javascript控制台上,这种方式也称为全局错误捕获;如:

window.onload=function(){
    show(); // 在onload事件中调用了一个不存在的函数
}
window.onerror=function(){
    alert("出现错误");
    return true;
}

获取错误信息:

window.onerror事件处理程序在调用时可以传5个参数,由这5个参数可以获取详细的错误信息;

  • message:错误信息,描述错误的一条消息;
  • URL:引发错误的Javascript所在的文档的URL;
  • line:文档中发生错误的行数;
  • column:发生错误的列数;
  • error:错误对象,这个error也称为全局错误对象;
window.onerror=function(sMessage, sUrl, sLine, sColumn, error){
console.log("Error:" + sMessage + " URL:" + sUrl + " Line:" + sLine + " Column:" + sColumn);
console.log(error);
    return true;
}

onerror处理程序的返回值:

如果返回true,则阻止执行默认的事件处理程序,也就是将通知浏览器,事件处理程序已经处理了错误,不需要其他操作,反之会显示错误消息;

某些元素也支持onerror; 但其处理函数没有任何关于error信息的参数,如:

document.images[0].onerror=function(event){
console.log(event);  // Event
console.log(event.type);  // error
}

这里的event参数是一个类型为Event事件对象,其存储的信息除了type返回了error,并没有其他和错误相关的信息;

全局错误处理window.onerror通常不能恢复脚本继续执行,但会给开发者发送错误信息;

window.onerror=function(error){
  console.log(error);
}
show();
console.log("中止,不会被执行");
window.onload=function(){
  console.log("也不会被执行");
}

可以是简单的打印,也可以把错误保存到日志记录里;

window.onerror就是绑定在window对象的error事件,也可以使用标准的添加事件侦听的方式window.addEventListener(eventtype, handler),其需要两个参数,eventtype为事件类型,在此为error,handler是事件处理函数,其需要一个参数event,一个ErrorEvent类型的对象,其保存着有关事件和错误的所有信息,如:

window.addEventListener("error", function(event){
  console.log(event);  // ErrorEvent
  console.log(event.error);  // Error对象
  console.log(event.error.name);
  console.log(event.error.message);
  console.log(event.error.stack);
  console.log(event.lineno);  // 行
  console.log(event.colno);  // 列
  console.log(event.filename);
});

在实际的开发中,这两种方式都会被使用,只不过addEventListener有定的兼容必问题,所以要兼顾所有的浏览器且不太关注事件对象本身的话,就使用window.onerror;

当加载自不同域的脚本中发生语法错误时,为避免信息泄露,语法错误的细节将不会报告,只会返回简单的"Script error.";

<script>
window.onerror=function(msg, url, lineNo, columnNo, error){
  console.log(msg);  // Script error
  console.log(url);  // ""
  console.log(lineNo);  // 0
  console.log(columnNo);  // 0
  console.log(error);  // null
}
</script>
<script src="https://www.zeronetwork.cn/demo/demo.js"></script>

可以针对同域和不同域的错误分开处理,如:

<script>
window.onerror=function(msg, url, lineNo, columnNo, error){
  var str_error=msg.toLowerCase();
  var sub_string="script error";
  if(str_error.indexOf(sub_string) > -1)
    alert("脚本发生错误,详情请在控制台查看");
  else{
    var message=[
      '消息:' + msg,
      'URL:' + url,
      '行:' + lineNo,
      '列:' + columnNo,
      '错误对象:' + error
    ].join(" - ");
    alert(message);
  }
}
show();
</script>
<script src="https://www.zeronetwork.cn/demo/demo.js"></script>

从上在的执行结果来看,error事件执行了两次,原因是使用了两个script,也就是当一个script有错误发生时,它只会阻止当前的script块,而不会阻止其他的script块;如:

<script>
show();  // 会捕获
console.log("不会被执行");
myshow();  // 不会捕获
</script>
<script src="https://www.zeronetwork.cn/demo/demo.js"></script>
<script>
console.log("执行了");
demo();  // 会捕获
console.log("不会被执行");
</script>

body元素的onerror特性,也可以充当事件处理函数,如:

<body onerror="alert('出现了错误');return true;">

注意,先注释掉window.onerror等代码;

此时,可以直接使用event、source、lineno、colno、error等属性;

<body onerror="alert(event + '\n' + source + '\n' + lineno + '\n' + colno + '\n' + error);return true;">

当然了,也可以为body绑定error事件,此时各属性,必须明确指定,如:

document.body.onerror=function(msg, url,lineno,colno,error){
  alert(msg + '\n' + url + '\n' + lineno + '\n' + colno + '\n' + error);
  return true;
}

try-catch语句:

try语句中为期待正常执行的代码块,当在try语句中发生错误,其余代码会中止执行,catch语句就处理该错误,如果没有错误,就跳过catch语句;try和catch必须成对出现;

try{
    //code
    [break]
}catch([exception]){
    //code
}[finally]{
    //code
}
// 如
try {
    show();
    alert("不能执行");
} catch (error) {
    alert("出现一个错误:" + error);
} finally{
    alert("管你呢");
}

try语句块内的错误只会中止try语句块中发生错误之后的逻辑代码,并不会中止整个脚本的运行;执行try-catch语句,必须是运行时,运行时错误,也被称为异常;try-catch语句中指定只能有一个catch子句;try-catch语句适合处理无法预知、无法控制的错误;finally常被用于无论结果是否有异常,都要执行的代码,如:

try{
  alert("try");
  show();
  alert("no exec");
}catch(error){
  alert("catch");
}finally{
  alert("finally");
}
alert("continute");

代码执行的两条路径:如果没有异常,执行路径为:try->finally,反之为:try的部分->catch->finally;

一般用于关闭打开的链接和释放资源;

var connection={open: function(){},close: function(){},send: function(data){}}
// var data="大师哥王唯"; // 注释这一行,让它产生异常
connection.open();
try{
    connection.send(data);
}catch(exception){
    console.log("出现一个错误");
}finally{
    connection.close();
    console.log("关闭了");
}

还有一个典型的应用,读写文件,如:

function openFile(){};
function writeFile(data){};
function closeFile(){};
openFile();
try{
  writeFile();
}catch(error){
  console.log(error);
}finally{
  closeFile();
}

在try-catch-finally语句块中的变量是全局变量:

try{
  var name="王唯";
  show();
  var city="蚌埠";
}catch(error){
  var age=18;
  console.log(name);
}finally{
  var sex="男";
  console.log(name);
  console.log(age);
}
console.log(name);
console.log(city);
console.log(age);
console.log(sex);

try-catch-finally与return:

如果直接在try-catch-finally语句块中执行return,会抛出异常,如:

try {
  console.log("try");
  // return;  // Illegal return statement 非法返回语句
  console.log("try agin");
} catch (error) {
  console.log(error);
  // return;  // Illegal return statement
}finally{
  console.log("finally");
  // return;  // Illegal return statement
}

如:

function foo(){
  try {
    console.log("try");
    return 1;
    show();
    console.log("try agin");
  } catch (error) {
    console.log(error);
    return 2;
  }finally{
    console.log("finally");
    return 3;
  }
}
console.log(foo());  // 3

try-finally:

没有catch从句,只有try-finally也可以,目的是,只确保执行开始和最终的过程,而不处理错误,如:

try{
  console.log("try");
  show();
}finally{
  console.log("finally"); // 会执行
}
console.log("over"); // 不会执行,已中止

但此时,还是会抛出异常的,但此时,会在执行完finally后中止执行,并会查找外部的catch语句;

嵌套try-catch语句:

在catch子句中,也有可能会发生错误,所以就可以使用嵌套的try-catch语句,如:

try {
    show();
    console.log("不能执行");
} catch (error) {
    console.log("出现一个错误:" + error);
    try {
        var arr=new Array(10000000000000000);
        arr.push(error);
    } catch (error) {
        console.log("又出现了一个错误:" + error);
    }
} finally{
    console.log("管你呢");
}

也可以在try中嵌套try-catch-finally语句,如:

try{
  try{
    console.log("try");
    show();
  }catch(error){
    console.log("error");
  }finally{
    console.log("finally");
  }
}catch(error){
  console.log(error);
}

一个比较典型的应用,就是处理json数据,如:

// var json='{"name":"wangwei", "age": 18, "sex": "男"}';
var json='{bad json}';  // Uncaught SyntaxError
var data=JSON.parse(json);
console.log(data.name);
console.log(data.age);
console.log(data.sex);

一量json数据发生错误,整个应用都会崩溃,所以应该使用try-catch,如:

<div id="msg">您的信息:</div>
<script>
window.onload=function(){
  var msg=document.getElementById("msg");
  try{
    // var json='{"name":"王唯", "age": 18, "sex": "男"}';
    var json='{bad json}';  // Uncaught SyntaxError
    var data=JSON.parse(json);
    msg.innerHTML +="姓名:" + data.name + ",年龄:" + data.age + ",性别:" + data.sex;
  }catch(error){
    msg.innerHTML="开小差了,找不到你的信息";
  }
}
</script>

当使用了try-catch语句,就不会将错误提交给浏览器,也就不会触发error事件,如:

window.onerror=function(error){
  console.log(error);  // 不会触发
}
try{
  show();
}catch(error){
  console.log(error);
}

Error错误对象:

在catch中会捕获一个Error错误对象;该对象在Javascript解析或运行时,一旦发生错误,引擎就会抛出这个对象;如果没有相关联的try-catch捕获该对象,就由浏览器输出这个对象;

// ...
console.log("错误:" + error + " name:" + error.name + " message:" + error.message);

也可以通过Error的构造器创建一个错误对象,这个Error对象也可用于用户自定义的异常;语法:new Error([message[, filename[, lineNumber]]]);

  • message:可选,错误描述信息;
  • fileName:可选,非标准,被创建的Error对象的fileName属性值,默认是调用Error构造器代码所在的文件的名字; 但大部分浏览器没有实现;
  • lineNumber:可选,非标准,被创建的Error对象的lineNumber属性值,默认是调用Error构造器代码所在的文件的行号;但大部分浏览器没有实现;

实例化Error对象,也可以不使用new关键字,如:

var error=new Error("自定义错误对象");
var error=Error("不使用new");
console.log(error);
console.log(error.name);  // Error
console.log(error.message);  // 自定义错误对象

Error错误对象属性:

  • name:表示错误类型的字符串;
  • message:实际的错误信息;

Error类还有6个子类,其可以通过错误对象的name属性返回具体异常类的名称:

  • EvalError:错误发生在eval()函数中;
  • RangeError:数值超出javascript可表示的范围;;
  • ReferenceError:使用了非法或不能识别的引用;
  • SyntaxError:发生了语法错误;
  • TypeError:操作数的类型不是预期所需的;
  • URIError:在encodeURI()或decodeURI()函数中发生了错误;
// EvalError
try{
  throw new EvalError("Eval异常");
}catch(error){
  console.log(error);
  console.log(error instanceof EvalError);  // true
  console.log(error.name);  // EvalError
  console.log(error.message);  // Eval异常
}
// RangeError
var num=1;
try{
  num.toPrecision(500);  //  [pr??s??n] 精度
}catch(error){
  console.log(error); 
}
// ReferenceError
var x;
try {
  x=y + 1;
} catch (error) {
  console.log(error);
}
// SyntaxError
try{
  eval("alert('wangwei)");
}catch(error){
  console.log(error);
}
// TypeError
var num=1;
try{
  num.toUpperCase(); // 无法将数字转为大写
}catch(error){
  console.log(error);
}
// URIError (malformed [?m?l?f??md]格式不正确,畸形的)
try{
  decodeURI("%%%"); // 使用了非法字符
}catch(error){
  console.log(error);
}

Error对象的message属性是浏览器生成的用于表示错误描述的信息,因为这个属性是特定于浏览器的,所以不同的浏览器上可能产生不同的错误信息,如:

try {
    eval("a ++ b");
    show();
    console.log("执行了吗?");
} catch (error) {
    // SyntaxError:Unexpected identifier或
    // SyntaxError:unexpected token: identifier
    console.log(error.name + ":" + error.message);
}

使用name属性判断错误类型:

try {
    eval("a++b");
} catch (error) {
    console.log(error instanceof SyntaxError); // true
    if(error.name=="SyntaxError")
        console.log(error.name + ":" + error.message);
    else
        console.log("未知错误:" + error.message);
}

抛出异常:

throw语句的作用是手动中断程序执行,抛出一个错误,一般用于有目的的抛出异常,也就是允许开发者可以创建自定义错误;

throw可以抛出任何类型的值,也就是说,它的参数可以是任何值;语法:throw error_object;error_object可以是字符串、数字、布尔或对象;

throw "出现一个错误";
throw 50666;
throw true;
throw new Object();
throw {
    toString:function(){
        return 'Error!';
    }
}
function getRectArea(width, height){
  if(isNaN(width) || isNaN(height))
    throw '参数应该是number类型';
  return width * height;
}
getRectArea("wangwei",10);

对于Javascript引擎来说,只要遇到throw语句,程序就会终止;

也可以抛出一个Error错误对象;Error对象的构造函数只有一个参数,

throw new Error("请再次尝试");

其他Error子类对象也可以抛出:

throw new SyntaxError("...");
throw new TypeError("...");
throw new RangeError("...");
throw new EvalError("...");
throw new URIError("...");
throw new ReferenceError("...");

对于Error类和其子类来说,错误对象的name就是其构造函数的名称,message是其构造函数的参数;

当抛出异常后,throw之后的语句将不会执行,并跳到相关联的catch语句中进行处理,如:

<h1>请输入18-99之间的数字</h1>
<input id="txtInput" type="text" />
<button id="btn">确定</button>
<p id="msg"></p>
<script>
function myFun(){
  var msg,x;
  msg=document.getElementById("msg");
  msg.innerHTML="";
  x=document.getElementById("txtInput").value;
  try{
    if(x=="") throw "空的";
    if(isNaN(x)) throw "不是数字";
    x=Number(x);
    if(x < 18) throw "太小";
    if(x > 99) throw "太大";
    msg.innerHTML="输入的值正确:" + String(x);
  }
  catch(error){
    msg.innerHTML="输入的值不正确:" + error; 
  }
}
var btn=document.getElementById("btn");
btn.onclick=myFun;
</script>

也可以在某个语句块的外部捕获throw异常,如:

function sum(a,b){
    if(arguments.length < 2)
        throw new Error("需要两个参数");
    else
        return a + b;
}
try{
    console.log(sum(18));
}catch(error){
    // Error:需要两个参数
    console.log(error.name + ":" + error.message);
}

可以通过instanceof判断异常的类型来特定处理某一类的异常,例如可以区分浏览器抛出的异常和开发人员抛出的异常,如:

function sum(a,b){
    if(arguments.length < 2)
      throw new Error("需要两个参数");
    if(isNaN(a) || isNaN(b))
      throw "参数是不是Number类型";
    return a + b;
}
try{
    console.log(sum(18,12));
}catch(error){
  if(error instanceof SyntaxError)
    console.log("语法错误:" + error.name + ":" + error.message);
  else if(error instanceof Error)
    console.log(error.name + ":" + error.message);
  else
      console.log(error);
}

注:判断Error类型要放到if的最后一个条件;

即使在catch语句中,还可以根据实际情况,再次抛出异常,此时,其可以被外部的try-catch语句块捕获(如果存在的话);

当发生异常时,代码会立即停止,仅当有try-catch语句捕获到异常时,代码才会继续执行;其背后运行的原理是,当发生异常,JavaScript解释器会立即停止执行的逻辑,并跳转到就近的try-catch异常处理程序,如果发生异常的代码块中没有相关联的catch从句,解释器会检查更高层的闭合代码块,看它是否有相关联的异常处理程序,以此类推,直到找到一个异常处理程序为止;如果发生异常的函数中没有处理它的try-catch语句,异常将向上传播到调用该函数的代码,如此,异常就会沿着Javascript的语法结构或调用栈向上传播;如果没有找到任何异常处理程序,JavaScript将把异常当成程序错误来处理,并通过浏览器报告给用户;

自定义错误类型:

可以基于Error类来创建自定义的错误类型,此时可以使用throw抛出自定义的异常类,或通过instanceof来检查这个异常类的类型,新类型需要实现name和message属性;

function CustomError(message){
  this.name="CustomError";
  this.message=message || 'Default Message';
  this.stack=(new Error()).stack;
}
// CustomError.prototype=new Error();
// 或者
CustomError.prototype=Object.create(Error.prototype);
CustomError.prototype.constructor=CustomError;
try{
  var name="jingjing";
  if(name !=="wangwei")
    throw new CustomError("自定义的错误类型");
}catch(error){
  console.log(error.message);
}

小示例:

function UserException(message){
  this.name="UserException";
  this.message=message;
}
function getMothName(m){
  m=m - 1;  // 调整月份数字到数组索引(1=Jan,12=Dec)
  var months=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  if(months[m] !=undefined)
    return months[m];
  else
    throw new UserException("Invalid Month No");
}
try{
  var myMonth=15;
  var monthName=getMothName(myMonth);
}catch(error){
  var monthName="未知";
  console.log(error.name + ":" + error.message);
}

小示例:验证电话号码,如:

function Telephone(num){
  num=String(num);
  var pattern=/^((0\d{2,3}-\d{7,8})|(1[3584]\d{9}))$/;
  if(pattern.test(num)){
    this.value=num.match(pattern)[0];
    this.valueOf=function(){
      return this.value;
    };
    this.toString=function(){
      return String(this.value);
    }
  }else{
    throw new TelephoneFormatException(num);
  }
}
function TelephoneFormatException(value){
  this.name="TelephoneFormatException";
  this.message="电话号码格式不正确";
  this.value=value;
  this.toString=function(){
    return this.value + ":" + this.message;
  }
}
// 应用
var TELEPHONE_INVALID=-1;
var TELEPHONE_UNKNOWN_ERROR=-2;
function verifyTelephone(num){
  try{
    num=new Telephone(num);
  }catch(error){
    if(error instanceof TelephoneFormatException)
      return TELEPHONE_INVALID;
    else
      return TELEPHONE_UNKNOWN_ERROR;
  }
  return num.toString();
}
console.log(verifyTelephone("010-66668888"));
console.log(verifyTelephone("13812345678"));
console.log(verifyTelephone("138123456")); // -1
console.log(verifyTelephone("wangwei")); // -1

常见错误:

由于javaScript是松散类型的,也不会验证函数的参数,因此错误只会在运行时出现;一般来说,需要关注三种错误:类型转换错误、数据类型错误、通信错误;

类型转换错误:

一般发生在使用某个操作符,或者使用其他可能自动转换值的数据类型的语言结构时;

function output(str1,str2,str3){
  var result=str1 + str2;
  if(str3)
    result +=str3;
  return result;
}
console.log(output(1,2,3));
console.log(output(1,2));
console.log(output(1,2,0));
console.log(output(1,2,"wangwei"));

这就是一个非常典型的与期望不一致的方式;

数据类型错误:

在流控制语句中使用非布尔值,是极为常见的一个错误来源,为避免此类错误,就要做到在条件比较时确定传入的是布尔值,例如,把if语句改成:if(typeof str3=='number');

所以在使用某个变量或对象时,一定要适当地检查它的数据类型,如:

function reverseSort(values){
  // if(values){
  //   values.sort();
  //   values.reverse();
  // }
  if(arguments.length > 0){
    if(!Array.isArray(values))
      return [];
    else{
      values.sort();
      values.reverse();
      return values;
    }
  }
  return [];
}
var arr=[3,2,6,9,4];
// var arr=100;  // Uncaught TypeError: values.sort is not a function
console.log(reverseSort(arr));

另一个常见的错误就是将参数与null值进行比较。与null进行比较只能确保相应的值不是null和undefined。要确保传入的值有效,仅检测null值是不够的;

function reverseSort(values){
  // if(values !=null){  // 任何非数组值都会导致错误
  if(values instanceof Array) // 非数组值被忽略
    values.sort();
    values.reverse();
  }
  return values;
}
var arr=[3,2,6,9,4];
// var arr=100;  // Uncaught TypeError: values.sort is not a function
console.log(reverseSort(arr));
// 或
function reverseSort(values, fun){
  if(values instanceof Array){
    if(fun !=null && typeof fun==="function")
      values.sort(fun);
    else
      values.sort();
  }
  return values;
}
var arr=[3,2,6,9,4];
console.log(reverseSort(arr, function(a,b){
  return a > b ? -1 : 1;
}));

通信错误:最典型的就是Ajax应用,用其可以动态加载信息,但是,javascript与服务器之间的任何一次通信,都有可能会产生错误;

调试技巧:

使用警告框: 这是最简单、流行的方式,如:

function test(){
    alert("函数内");
    var iNum1=5, iNum2=10;
    alert(iNum1);
    var iResult=iNum1 + iNum2;
    alert(iResult);
}
test();

抛出自定义错误:

function assert(bCondition, sErrorMessage){
    if(!bCondition)
        throw new Error(sErrorMessage);
}
function divide(iNum1, iNum2){
    assert(arguments.length==2, "divide需要两个参数");
    assert((!isNaN(iNum1) && !isNaN(iNum2)), "需要Number类型");
    return iNum1 / iNum2;
}
console.log(divide(10,2));
console.log(divide(10,"c"));  // 异常
// 或
try{
  console.log(divide(10,"c"));
}catch(error){
  console.log(error.name + ":" + error.message);
}

Javascript校验器:

jslint的主要目的是指出不合规范的js语法和可能的语法错误,包括一些不良代码;官网:http://www.jslint.com/

如以下,会给出警告:

  • 语句未使用块标记;
  • 一行的结尾未以分号结束;
  • 用var声明一个已在使用的变量;
  • with语句;

调试器:

Javascript自身不具备调试器,但目前所有的浏览器可以使用自身的调试器;

IE调试:

启用IE的调试功能:

菜单“工具”|“Internet选项”命令,打开“Internet选项”对话框,在“高级”选项卡中,找到两个“禁用脚本调试”复选框并取消;开始调试,调试的主要工作是反复地跟踪代码,找出错误并修正;

设置断点:

在调试程序窗口中,将光标移动到需要添加断点的行上,按一次F9键或单击,当前行的背景色变为红色,并且在窗口左边界上标上红色的圆点,当程序运行到断点时就会暂停;

运行调试:

单击继续或按F5进行逐步运行调试;F10步进、F11步入,都可以继续向下执行;将鼠标移动到变量名上时,会显示变量当前时刻的值;或者在右侧的“监视”窗格中可以观察该变量的值;点击变量信息框中的变量值或右侧“监视”空格中的变量值可以修改变量的当前值;更多的调试操作:查看调用关系、监视特定变量的值等;

// 示例
var balance=200.0;    //
var willPay=20.0;
function pay(_balance, _pay){
    return _balance - _pay;
}
function showBalance(){
    debugger;
    var blnc=pay(balance,willPay);
    alert("当前余额:" + blnc);
}
showBalance();

日志输出:

程序运行时,有些中间数据需要记录,以便检查程序运行的状态;对于JavaScript记录中间数据通常是以日志的形式记录需要记录的数据,再发送到服务器上保存起来;日志记录的内容可以是任意的信息,根据开发者的需要而定;