第54节 错误处理及调试-Web前端开发之Javascript-零点程序-王唯

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

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

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

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

错误类型:

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

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

错误报告:

因为每个浏览器都有自己的内置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.onerror事件处理程序在调用时可以传5个参数,由这5个参数可以获取详细的错误信息;

  • message:错误信息,描述错误的一条消息;
  • URL:引发错误的Javascript所在的文档的URL;
  • line:文档中发生错误的行数;
  • column:发生错误的列数;
  • error:错误对象,这个error也称为全局错误对象;

onerror处理程序的返回值:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

try-catch语句:

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

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

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

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

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

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

try-catch-finally与return:

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

如:

try-finally:

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

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

嵌套try-catch语句:

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

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

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

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

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

Error错误对象:

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

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

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

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

Error错误对象属性:

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

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

  • EvalError:错误发生在eval()函数中;
  • RangeError:数值超出javascript可表示的范围;;
  • ReferenceError:使用了非法或不能识别的引用;
  • SyntaxError:发生了语法错误;
  • TypeError:操作数的类型不是预期所需的;
  • URIError:在encodeURI()或decodeURI()函数中发生了错误;

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

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

抛出异常:

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

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

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

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

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

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

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

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

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

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

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

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

自定义错误类型:

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

小示例:

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

常见错误:

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

类型转换错误:

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

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

数据类型错误:

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

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

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

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

调试技巧:

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

抛出自定义错误:

Javascript校验器:

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

如以下,会给出警告:

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

调试器:

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

IE调试:

启用IE的调试功能:

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

设置断点:

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

运行调试:

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

日志输出:

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

电脑屏幕显示节电模式然后黑屏怎么办

排序的方法多种多样,通常情况下,我们经常使用到的数组排序函数有:sort()、rsort()、asort()、arsort()、ksort()、krsort()。下面为大家一一详细介绍。

0=SORT_REGULAR-默认。把每一项按常规顺序排列(StandardASCII,不改变类型)

1=SORT_NUMERIC-把每一项作为数字来处理。

2=SORT_STRING-把每一项作为字符串来处理。

3=SORT_LOCALE_STRING-把每一项作为字符串来处理,基于当前区域设置(可通过setlocale()进行更改)。

原因:

估计是显卡,或连线故障,显示器没有收到信号吧。

现在的家电都设计了节电模式功能,显示器也一样当显示收不到主机发来的视频信号时,就会自动进入节电模式。

通过这些可以分析出,开机时,你的主机没有向显示器发送出视频信号,可能您的主机启动不正常。

主机不一定灯亮风扇转就启动了,如遇硬件故障,是过不了自检的,此时显卡不工作,显示器收不到显卡的视频信号自然是节电模式。

《常见问题》

解决方法:

看看你的电脑是否设置了自动关闭显示器功能,如果没有就是主机硬件问题,比如主板或显卡电容爆裂导致的黑屏、CPU过热等导致的主板自我保护关机。

扩展资料:

电脑黑屏是比较容易出现的现象,尤其在一些较老的电脑或组装电脑中。

电脑黑屏的故障原因有多种,如显示器损坏、主板损坏、显卡损坏、显卡接触不良、电源损坏、cpu损坏、零部件温度过高等。也有的是人为的造成电脑黑屏,如微软中国的正版验证。

2008年10月15日微软内部邮件,微软将针对中国市场在20日启动WindowsXP专业版及Office的正版验证计划,届时,安装了盗版WindowsXP专业版的电脑将被强行每小时“黑屏”(桌面背景变为纯黑色)一次,Office的菜单栏将被添加“不是正版”的标记,盗版软件的用户将分别遭遇电脑“黑屏”与“提醒标记”等警告。

以上就是电脑屏幕显示节电模式然后黑屏怎么办的详细内容,更多请关注其它相关文章!

推荐:【PHP教程】

一、防止SQL注入

什么是SQL注入攻击?

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

寻找SQL注入的方法:

1.通过get请求

2.通过post请求

3.其他http请求,如cookie

常见的SQL注入问题:

数据库查询参数的类型转换处理

  1. 转义字符处理不当
  2. Talkischeap,Showmethecode.
  3. 多说无益,代码亮出来吧!
  4. 测试:
  5. 在下边的网址后边加一个单引号,就会报数据库错误
  6. http://testphp.vulnweb.com/ar…
    1. 类型处理不当
    2. Mysql内置了一个命令,可以读取文件
    3. 该命令会获取数据库管理员的密码。
    4. 处理方法:
    5. 需要将客户端传过来的数据进行类型强制转换,而后再查询
      1. 查询语句组织不当
        1. 错误处理不当
        2. 即将站点的错误信息暴漏给用户,这样非常危险。
        3. 数据入库时将转换单引号、双引号、反斜杠为实体
        4. 在入库的时候如果不过滤\’\”\”这样的东西,这样会使数据库报错,或者注入等问题。
        5. 先将字符串用htmlspecialchars()转换为实体后存储到数据库,然后从数据库读出来时htmlspecialchars_decode()转为HTML标签。
        6. htmlspecialchars()函数把一些预定义的字符转换为HTML实体。
        7. 函数原型:
        8. 预定义的字符是:
        9. htmlspecialchars_decode()函数把一些预定义的HTML实体转换为字符(和htmlspecialchars相反)。
        10. 函数原型:
        11. 二、防止xss攻击
        12. 什么是xss攻击?
        13. 和上边的sql注入不同的是,xss攻击是合法的字符串,如经过htmlspecialchars()方法实体化后,可以保存在数据库中,但是,当访问含有该字符串的内容页面时,就会出现问题,如字符串里边还有JavaScript,frame代码,原来的页面就会被篡改。
        14. 比如你写个留言本,有人去留言写<scriptsrc=\”xx\”></script><iframe>,这个被显出来容易挂病毒都很容易,和数据库无关。
        15. XSS概念
        16. XSS又称CSS,全称CrossSiteScript(跨站脚本攻击),XSS攻击类似于SQL注入攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie信息、破坏页面结构、重定向到其它网站等。
        17. 理论上,只要存在能提供输入的表单并且没做安全过滤或过滤不彻底,都有可能存在XSS漏洞。
        18. 下面是一些最简单并且比较常见的恶意字符XSS输入:
        19. 1.XSS输入通常包含JavaScript脚本,如弹出恶意警告框:<script>alert(\”XSS\”);</script>
        20. 2.XSS输入也可能是HTML代码段,譬如:
        21. (1).网页不停地刷新<metahttp-equiv=\”refresh\”content=\”0;\”>
        22. (2).嵌入其它网站的链接<iframesrc=http://xxxxwidth=250height=250></iframe>
        23. 除了通过正常途径输入XSS攻击字符外,还可以绕过JavaScript校验,通过修改请求达到XSS攻击的目的,如下图:
        24. 了解到XSS攻击的原理和危害后,其实要预防也不难,下面提供一个简单的PHP防止XSS攻击的函数:
        25. 除了通过正常途径输入XSS攻击字符外,还可以绕过JavaScript校验,通过修改请求达到XSS攻击的目的。
        26. 了解到XSS攻击的原理和危害后,其实要预防也不难,下面提供一个简单的PHP防止XSS攻击的函数:
        27. 避免被XSS:
        28. 1.给用户开放的编辑器尽量过滤掉危险的代码
        29. 如果是html编辑器,一般的做法是保留大部分代码,过滤部分可能存在危险的代码,如script,iframe等等
        30. 三、PHPMySQL预处理语句
        31. 预处理语句对于防止MySQL注入是非常有用的。
        32. 预处理语句及绑定参数
        33. 预处理语句用于执行多个相同的SQL语句,并且执行效率更高。
        34. 预处理语句的工作原理如下:
        35. 预处理:创建SQL语句模板并发送到数据库。预留的值使用参数\”?\”标记。例如:
        36. 数据库解析,编译,对SQL语句模板执行查询优化,并存储结果不输出。
        37. 执行:最后,将应用绑定的值传递给参数(\”?\”标记),数据库执行语句。应用可以多次执行语句,如果参数的值不一样。
        38. 相比于直接执行SQL语句,预处理语句有两个主要优点:
        39. 预处理语句大大减少了分析时间,只做了一次查询(虽然语句多次执行)。
        40. 绑定参数减少了服务器带宽,你只需要发送查询的参数,而不是整个语句。
        41. 预处理语句针对SQL注入是非常有用的,因为参数值发送后使用不同的协议,保证了数据的合法性。
        42. PDO预处理机制
        43. 可以使用多种方式实现预处理:指的是在绑定数据进行执行的时候,可以有多种方式。
        44. 预处理语句中为变量
        45. 使用数组指定预处理变量
        46. 1、准备预处理语句(发送给服务器,让服务器准备预处理语句)
        47. 2、发送预处理语句
        48. 3、给预处理绑定数据
        49. 4、执行预处理:将要操作的数据发送给预处理语句,再执行预处理语句
        50. PDO预处理原理
        51. PDO的预防sql注入的机制也是类似于使用mysql_real_escape_string进行转义,PDO有两种转义的机制,第一种是本地转义,这种转义的方式是使用单字节字符集(PHP<5.3.6)来转义的(单字节与多字节),来对输入进行转义,但是这种转义方式有一些隐患。隐患主要是:在PHP版本小于5.3.6的时候,本地转义只能转换单字节的字符集,大于5.3.6的版本会根据PDO连接中指定的charset来转义。
        52. 第二种方式是PDO,首先将sql语句模板发送给MysqlServer,随后将绑定的字符变量再发送给Mysqlserver,这里的转义是在MysqlServer做的,它是根据你在连接PDO的时候,在charset里指定的编码格式来转换的。这样的转义方式更健全,同时还可以在又多次重复查询的业务场景下,通过复用模板,来提高程序的性能。如果要设置MysqlServer来转义的话,就要首先执行:
        53. 原始链接方法:
        54. 可见这次PHP是将SQL模板和变量是分两次发送给MySQL的,由MySQL完成变量的转义处理,既然变量和SQL模板是分两次发送的,那么就不存在SQL注入的问题了,但需要在DSN中指定charset属性,如:
        55. 示例:
        56. 总结:当调用prepare()时,查询语句已经发送给了数据库服务器,此时只有占位符?发送过去,没有用户提交的数据;当调用到execute()时,用户提交过来的值才会传送给数据库,他们是分开传送的,两者独立的,SQL攻击者没有一点机会。
        57. 小结
        58. ①、关于sql注入可以使用htmlspecialchars()或addslashes()方法,如果连接mysql,可以用mysql_real_escape_string(),还有在php.ini中配置magic_quotes_gpc开启自动转义的扩展。
        59. PHP环境打开自动转义,PHP.INI中查看
        60. 当magic_quotes_gpc=on时将自动进行转义(默认是on),可在程序中用get_magic_quotes_gpc()检查他的状态
        61. 程序为:
        62. ②、关于xss攻击可以写一个去处script,frame等代码的方法:
        63. 直接用这个函数editor_safe_replace代替htmlspecialchars,既保证安全又能用大部分html代码
        64. 所以,对于PHP的安全而言,一定要对用户提交的数据进行过滤校验处理,即先防止SQL注入,后再进行XSS过滤,这两个都需要两手一起抓,且两手都要硬,否则,你的网站将会存在很大的安全风险。
        65. 以上就是php安全问题思考的详细内容,更多请关注其它相关文章!

4=SORT_NATURAL-把每一项作为字符串来处理,使用类似natsort()的自然排序。

5=SORT_FLAG_CASE-可以结合(按位或)SORT_STRING或SORT_NATURAL对字符串进行排序,不区分大小写。

定义和用法:

sort()函数对索引数组进行升序排序。

rsort()函数对索引数组进行降序排序,其他用法一样。

注释:本函数为数组中的单元赋予新的键名。原有的键名将被删除。

如果成功则返回TRUE,否则返回FALSE。

提示:请使用rsort()函数对索引数组进行降序排序。

sort()函数用于对数组单元从低到高进行排序。

rsort()函数用于对数组单元从高到低进行排序。

asort()函数用于对数组单元从低到高进行排序并保持索引关系。

PHPasort()函数用于对数组单元从低到高进行排序并保持索引关系,如果成功则返回TRUE,失败则返回FALSE。

arsort()函数用于对数组单元从高到低进行排序并保持索引关系。

PHParsort()函数行为与asort()相反,对数组单元进行由高到低排序并保持索引关系,请参考asort()函数使用。

ksort()函数用于对数组单元按照键名从低到高进行排序。

PHPksort()函数用于对数组单元按照键名从低到高进行排序,如果成功则返回TRUE,失败则返回FALSE。本函数会保留原来的键名,因此常用于关联数组。

krsort()函数用于对数组单元按照键名从高到低进行排序。

PHPkrsort()函数行为与ksort()相反,对数组单元按照键名进行由高到低排序,请参考ksort()函数使用。

使用用户自定义的比较函数对数组$a中的元素进行排序:

以上是工作中常用的数组排序方法,想了解更多相关问题:PHP视频教程

以上就是php排序函数详细讲解(附实例)的详细内容,更多请关注其它相关文章!

JavaScript错误处理完整指南

大家好,我是 Echa。

本文将带你了解 JavaScript 中常见的错误类型,处理同步和异步 JavaScript/Node.js 代码中错误和异常的方式,以及错误处理最佳实践!

JavaScript 中的错误是一个对象,在发生错误时会抛出该对象以停止程序。在 JavaScript 中,可以通过构造函数来创建一个新的通用错误:

当然,也可以省略 new 关键字:

Error 对象有三个属性:

  • message:带有错误消息的字符串;
  • name: 错误的类型;
  • stack:函数执行的堆栈跟踪。

例如,创建一个 TypeError 对象,该消息将携带实际的错误字符串,其 name 将是“TypeError”:

堆栈跟踪是发生异常或警告等事件时程序所处的方法调用列表:

它首先会打印错误名称和消息,然后是被调用的方法列表。每个方法调用都说明其源代码的位置和调用它的行。可以使用此数据来浏览代码库并确定导致错误的代码段。此方法列表以堆叠的方式排列。它显示了异常首先被抛出的位置以及它如何通过堆栈方法调用传播。为异常实施捕获不会让它通过堆栈向上传播并使程序崩溃。

对于 Error 对象,Firefox 还实现了一些非标准属性:

  • columnNumber:错误所在行的列号;
  • filename:发生错误的文件
  • lineNumber:发生错误的行号

JavaScript 中有一系列预定义的错误类型。只要使用者没有明确处理应用程序中的错误,它们就会由 JavaScript 运行时自动选择和定义。

JavaScript中的错误类型包括:

  • EvalError
  • InternalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError

这些错误类型都是实际的构造函数,旨在返回一个新的错误对象。最常见的就是 TypeError。大多数时候,大部分错误将直接来自 JavaScript 引擎,例如 InternalError 或 SyntaxError。

JavaScript 提供了 instanceof 运算符可以用于区分异常类型:

下面来了解 JavaScript 中最常见的错误类型,并了解它们发生的时间和原因。

SyntaxError 表示语法错误。这些错误是最容易修复的错误之一,因为它们表明代码语法中存在错误。由于 JavaScript 是一种解释而非编译的脚本语言,因此当应用程序执行包含错误的脚本时会抛出这些错误。在编译语言的情况下,此类错误在编译期间被识别。因此,在修复这些问题之前,不会创建应用程序二进制文件。

SyntaxError 发生的一些常见原因是:

  • 缺少引号
  • 缺少右括号
  • 大括号或其他字符对齐不当

TypeError 是 JavaScript 应用程序中最常见的错误之一,当某些值不是特定的预期类型时,就会产生此错误。

TypeError 发生的一些常见原因是:

  • 调用不是方法的对象。
  • 试图访问 null 或未定义对象的属性
  • 将字符串视为数字,反之亦然

ReferenceError 表示引用错误。当代码中的变量引用有问题时,会发生 ReferenceError。可能忘记在使用变量之前为其定义一个值,或者可能试图在代码中使用一个不可访问的变量。在任何情况下,通过堆栈跟踪都可以提供充足的信息来查找和修复有问题的变量引用。

ReferenceErrors 发生的一些常见原因如下:

  • 在变量名中输入错误。
  • 试图访问其作用域之外的块作用域变量。
  • 在加载之前从外部库引用全局变量。

RangeError 表示范围错误。当变量设置的值超出其合法值范围时,将抛出 RangeError。它通常发生在将值作为参数传递给函数时,并且给定值不在函数参数的范围内。当使用记录不完整的第三方库时,有时修复起来会很棘手,因为需要知道参数的可能值范围才能传递正确的值。

RangeError 发生的一些常见场景如下:

  • 试图通过 Array 构造函数创建非法长度的数组。
  • 将错误的值传递给数字方法,例如 toExponential()toPrecision()toFixed()等。
  • 将非法值传递给字符串函数,例如 normalize()

URIError 表示 URI错误。当 URI 的编码和解码出现问题时,会抛出 URIError。JavaScript 中的 URI 操作函数包括:decodeURIdecodeURIComponent 等。如果使用了错误的参数(无效字符),就会抛出 URIError。

EvalError 表示 Eval 错误。当 eval() 函数调用发生错误时,会抛出 EvalError。不过,当前的 JavaScript 引擎或 ECMAScript 规范不再抛出此错误。但是,为了向后兼容,它仍然是存在的。

如果使用的是旧版本的 JavaScript,可能会遇到此错误。在任何情况下,最好调查在eval()函数调用中执行的代码是否有任何异常。

InternalError 表示内部错误。在 JavaScript 运行时引擎发生异常时使用。它表示代码可能存在问题也可能不存在问题。

InternalError 通常只发生在两种情况下:

  • 当 JavaScript 运行时的补丁或更新带有引发异常的错误时(这种情况很少发生);
  • 当代码包含对于 JavaScript 引擎而言太大的实体时(例如,数组初始值设定项太大、递归太多)。

解决此错误最合适的方法就是通过错误消息确定原因,并在可能的情况下重构应用逻辑,以消除 JavaScript 引擎上工作负载的突然激增。

注意: 现代 JavaScript 中不会抛出 EvalError 和 InternalError。

虽然 JavaScript 提供了足够的错误类型类列表来涵盖大多数情况,但如果这些错误类型不能满足要求,还可以创建新的错误类型。这种灵活性的基础在于 JavaScript 允许使用 throw 命令抛出任何内容。

可以通过扩展 Error 类以创建自定义错误类:

可以通过以下方式使用它:

可以使用 instanceof 关键字识别它:

很多人认为错误和异常是一回事。实际上,Error 对象只有在被抛出时才会成为异常

在 JavaScript 中抛出异常,可以使用 throw 来抛出 Error 对象:

或者:

来看一个简单的例子:

在这里,我们检查函数参数是否为字符串。如果不是,就抛出异常。

从技术上讲,我们可以在 JavaScript 中抛出任何东西,而不仅仅是 Error 对象:

但是,最好避免这样做:要抛出正确的 Error 对象,而不是原语

异常一旦抛出,就会在程序堆栈中冒泡,除非在某个地方被捕获。

来看下面的例子:

在浏览器或 Node.js 中运行此代码,程序将停止并抛出错误:

这里还显示了发生错误的确切行。这个错误就是一个堆栈跟踪,有助于跟踪代码中的问题。堆栈跟踪从下到上:

toUppercase 函数在第 9 行调用,在第 3 行抛出错误。除了在浏览器的控制台中查看此堆栈跟踪之外,还可以在 Error 对象的 stack 属性问它。

介绍完这些关于错误的基础知识之后,下面来看看同步和异步 JavaScript 代码中的错误和异常处理。

同步代码会按照代码编写顺序执行。让我们再看看前面的例子:

在这里,引擎调用并执行 toUppercase,这一切都是同步发生的。 要捕获由此类同步函数引发的异常,可以使用 try/catch/finally:

通常,try 会处理正常的路径,或者可能进行的函数调用。catch 就会捕获实际的异常,它接收 Error 对象。而不管函数的结果如何,finally 语句都会运行:无论它失败还是成功,finally 中的代码都会运行。

JavaScript 中的生成器函数是一种特殊类型的函数。它可以随意暂停和恢复,除了在其内部范围和消费者之间提供双向通信通道。为了创建一个生成器函数,需要在 function 关键字后面加上一个 *

只要进入函数,就可以使用 yield 来返回值:

生成器函数的返回值是一个迭代器对象。要从生成器中提取值,可以使用两种方法:

  • 在迭代器对象上调用 next()
  • 使用 for…of 进行迭代

以上面的代码为例,要从生成器中获取值,可以这样做:

当我们调用生成器函数时,这里的 go 就是生成的迭代器对象。接下来,就可以调用 go.next() 来继续执行:

生成器也可以接受来自调用者的值和异常。除了 next(),从生成器返回的迭代器对象还有一个 throw() 方法。使用这种方法,就可以通过向生成器中注入异常来停止程序:

要捕获此类错误,可以使用 try/catch 将代码包装在生成器中:

生成器函数也可以向外部抛出异常。 捕获这些异常的机制与捕获同步异常的机制相同:try/catch/finally。

下面是使用 for…of 从外部使用的生成器函数的示例:

输出结果如下:

这里,try 块中包含正常的迭代。如果发生任何异常,就会用 catch 捕获它。

浏览器中的异步包括定时器、事件、Promise 等。异步世界中的错误处理与同步世界中的处理不同。下面来看一些例子。

上面我们介绍了如何使用 try/catch/finally 来处理错误,那异步中可以使用这些来处理错误吗?先来看一个例子:

此函数在大约 1 秒后会抛出错误。那处理此异常的正确方法是什么?以下代码是无效的:

我们知道,try/catch是同步的,所以没办法这样来处理异步中的错误。当传递给 setTimeout的回调运行时,try/catch 早已执行完毕。程序将会崩溃,因为未能捕获异常。它们是在两条路径上执行的:

我们可以监听页面中任何 HTML 元素的事件,DOM 事件的错误处理机制遵循与任何异步 Web API 相同的方案。

来看下面的例子:

这里,在单击按钮后立即抛出了异常,我们该如何捕获这个异常呢?这样写是不起作用的,也不会阻止程序崩溃:

与前面的 setTimeout 例子一样,任何传递给 addEventListener 的回调都是异步执行的:

如果不想让程序崩溃,为了正确处理错误,就必须将 try/catch 放到 addEventListener 的回调中。不过这样做并不是最佳的处理方式,与 setTimeout 一样,异步代码路径抛出的异常无法从外部捕获,并且会使程序崩溃。

下面会介绍 Promises 和 async/await 是如何简化异步代码的错误处理的。

HTML 元素有许多事件处理程序,例如 onclickonmouseenteronchange 等。除此之外,还有 onerror,每当 <img> 标签或 <script> 等 HTML 元素命中不存在的资源时,onerror 事件处理程序就会触发。

来看下面的例子:

当访问的资源缺失时,浏览器的控制台就会报错:

在 JavaScript 中,可以使用适当的事件处理程序“捕获”此错误:

或者使用 addEventListener 来监听 error 事件,当发生错误时进行处理:

此模式对于加载备用资源以代替丢失的图像或脚本很有用。不过需要记住:onerror 与 throw 或 try/catch 是无关的。

下面来通过最上面的 toUppercase 例子看看 Promise 是如何处理错误的:

对上面的代码进行修改,不返回简单的字符串或异常,而是分别使用 Promise.rejectPromise.resolve 来处理错误和成功:

从技术上讲,这段代码中没有任何异步的内容,但它可以很好地说明 Promise 的错误处理机制。

现在我们就可以在 then 中使用结果,并使用 catch 来处理被拒绝的 Promise:

输出结果如下:

在 Promise 中,catch 是用来处理错误的。除了 catch 还有 finally,类似于 try/catch 中的finally。不管 Promise 结果如何,finally 都会执行:

输出结果如下:

需要记住,任何传递给 then/catch/finally 的回调都是由微任务队列异步处理的。 它们是微任务,优先于事件和计时器等宏任务。

作为拒绝 Promise 时的最佳实践,可以传入 error 对象:

这样,在整个代码库中保持错误处理的一致性。 其他团队成员总是可以访问 error.message,更重要的是可以检查堆栈跟踪。

除了 Promise.reject 之外,还可以通过抛出异常来退出 Promise 执行链。来看下面的例子:

这里使用 字符串来 resolve 一个 Promise,然后执行链立即使用 throw 断开。为了停止异常的传播,可以使用 catch 来捕获错误:

这种模式在 fetch 中很常见,可以通过检查 response 对象来查找错误:

这里的异常可以使用 catch 来拦截。 如果失败了,并且没有拦截它,异常就会在堆栈中向上冒泡。这本身并没有什么问题,但不同的环境对未捕获的拒绝有不同的反应。

例如,Node.js 会让任何未处理 Promise 拒绝的程序崩溃:

所以,最好去捕获错误。

对于计时器或事件,不能捕获回调抛出的异常。上面有一个例子:

我们可以使用 Promise 来包装计时器:

这里通过 reject 捕获了一个 Promise 拒绝,它带有一个 error 对象。此时就可以用 catch 来处理异常了:

这里使用 value 作为 Promise 的返回值,使用 reason 作为拒绝的返回对象。

Promise.all 方法接受一个 Promise 数组,并返回所有解析 Promise 的结果数组:

如果这些 Promise 中的任何一个被拒绝,Promise.all 将拒绝并返回第一个被拒绝的 Promise 的错误。

为了在 Promise.all 中处理这些情况,可以使用 catch:

如果想要运行一个函数而不考虑 Promise.all 的结果,可以使用 finally:

Promise.any 和 Promise.all 恰恰相反。Promise.all 如果某一个失败,就会抛出第一个失败的错误。而 Promise.any 总是返回第一个成功的 Promise,无论是否发生任何拒绝。

相反,如果传递给 Promise.any 的所有 Promise 都被拒绝,那产生的错误就是 AggregateError。 来看下面的例子:

输出结果如下:

这里用 catch 处理错误。AggregateError 对象具有与基本错误相同的属性,外加一个 errors 属性:

此属性是一个包含所有被拒绝的错误信息的数组:

Promise.race 接受一个 Promise 数组,并返回第一个成功的 Promise 的结果:

那如果有被拒绝的 Promise,但它不是传入数组中的第一个呢:

这样结果还是 one,不会影响正常的执行。

如果被拒绝的 Promise 是数组的第一个元素,则 Promise.race 拒绝,就必须要必须捕获拒绝:

Promise.allSettled 是 ECMAScript 2020 新增的 API。它和 Promise.all 类似,不过不会被短路,也就是说当Promise全部处理完成后,可以拿到每个 Promise 的状态, 而不管其是否处理成功。

来看下面的例子:

这里向 Promise.allSettled 传递了一个包含两个 Promise 的数组:一个已解决,另一个已拒绝。

输出结果如下:

JavaScript 中的 async/await 表示异步函数,用同步的方式去编写异步,可读性更好。

下面来改编上面的同步函数 toUppercase,通过将 async 放在 function 关键字之前将其转换为异步函数:

只需在 function 前加上 async 前缀,就可以让函数返回一个 Promise。这意味着我们可以在函数调用之后链式调用 then、catch 和 finally:

当从 async 函数中抛出异常时,异常会成为底层 Promise 被拒绝的原因。任何错误都可以从外部用 catch 拦截。

除此之外,还可以使用 try/catch/finally 来处理错误,就像在同步函数中一样。

例如,从另一个函数 consumer 中调用 toUppercase,它方便地用 try/catch/finally 包装了函数调用:

输出结果如下:

JavaScript 中的异步生成器是能够生成 Promise 而不是简单值的生成器函数。它将生成器函数与异步相结合,结果是一个生成器函数,其迭代器对象向消费者公开一个 Promise。

要创建一个异步生成器,需要声明一个带有星号 * 的生成器函数,前缀为 async:

因为异步生成器是基于 Promise,所以同样适用 Promise 的错误处理规则,在异步生成器中,throw 会导致 Promise 拒绝,可以用 catch 拦截它。

要想从异步生成器处理 Promise,可以使用 then:

输出结果如下:

也使用异步迭代 for await…of。 要使用异步迭代,需要用 async 函数包装 consumer:

与 async/await 一样,可以使用 try/catch 来处理任何异常:

输出结果如下:

从异步生成器函数返回的迭代器对象也有一个 throw() 方法。在这里对迭代器对象调用 throw() 不会抛出异常,而是 Promise 拒绝:

输出结果如下:

可以通过以下方式来捕获错误:

我们知道,迭代器对象的 throw() 是在生成器内部发送异常的。所以还可以使用以下方式来处理错误:

Node.js 中的同步错误处理与 JavaScript 是一样的,可以使用 try/catch/finally。

对于异步代码,Node.js 强烈依赖两个术语:

  • 事件发射器
  • 回调模式

在回调模式中,异步 Node.js API 接受一个函数,该函数通过事件循环处理并在调用堆栈为空时立即执行。

来看下面的例子:

这里可以看到回调中错误处理:

如果使用 fs.readFile 读取给定路径时出现任何错误,我们都会得到一个 error 对象。这时我们可以:

  • 单地记录错误对象。
  • 抛出异常。
  • 将错误传递给另一个回调。

要想抛出异常,可以这样做:

但是,与 DOM 中的事件和计时器一样,这个异常会使程序崩溃。 使用 try/catch 停止它的尝试将不起作用:

如果不想让程序崩溃,可以将错误传递给另一个回调:

这里的 errorHandler 是一个简单的错误处理函数:

Node.js 中的大部分工作都是基于事件的。大多数时候,我们会与发射器对象和一些侦听消息的观察者进行交互。

Node.js 中的任何事件驱动模块(例如 net)都扩展了一个名为 EventEmitter 的根类。EventEmitter 有两个基本方法:on 和 emit。

下面来看一个简单的 HTTP 服务器:

这里我们监听了两个事件:listening 和 connection。除了这些事件之外,事件发射器还公开一个错误事件,在出现错误时触发。

如果这段代码监听的端口是 80,就会得到一个异常:

输出结果如下:

为了捕获它,可以为 error 注册一个事件处理函数:

这样就会输出:

最后,我们来看看处理 JavaScript 异常的最佳实践!

错处理的第一个最佳实践就是不要过度使用“错误处理”。通常,我们会在外层处理错误,从内层抛出错误,这样一旦出现错误,就可以更好地理解是什么原因导致的。

然而,开发人员常犯的错误之一是过度使用错误处理。有时这样做是为了让代码在不同的文件和方法中看起来保持一致。但是,不幸的是,这些会对应用程序和错误检测造成不利影响。

因此,只关注代码中可能导致错误的地方,错误处理将有助于提高代码健壮性并增加检测到错误的机会。

尽管许多浏览器都遵循一个通用标准,但某些特定于浏览器的 JavaScript 实现在其他浏览器上却失败了。例如,以下语法仅适用于 Firefox:

因此,在处理错误时,尽可能使用跨浏览器友好的 JavaScript 代码。

当发生错误时,我们应该得到通知以了解出了什么问题。这就是错误日志的用武之地。JavaScript 代码是在用户的浏览器中执行的。因此,需要一种机制来跟踪客户端浏览器中的这些错误,并将它们发送到服务器进行分析。

可以尝试使用以下工具来监控并上报错误:

  • Sentry(https://sentry.io/): 专注于异常(应用崩溃)而不是信息错误。它提供了应用中错误的完整概述,包括受影响的用户数量、调用堆栈、受影响的浏览器以及导致错误的提交等详细信息。
  • Rollbar(https://rollbar.com/): 用于前端、后端和移动应用的无代理错误监控工具。它提供人工智能辅助的工作流程,使开发人员能够在错误影响用户之前立即采取行动。它会显示受错误影响的客户数量、受影响的平台或浏览器的类型以及之前是否发生过类似错误或是否已经存在解决方案等数据。

Node.js 环境支持使用中间件向服务端应用中添加功能。因此可以创建一个错误处理中间件。使用中间件的最大好处是所有错误都在一个地方集中处理。可以选择启用/禁用此设置以轻松进行测试。

以下是创建基本中间件的方法:

可以像下面这样在应用中使用此中间件:

现在可以在中间件内定义自定义逻辑以适当地处理错误。而无需再担心在整个代码库中实现单独的错误处理结构。

我们可能永远无法涵盖应用中可能发生的所有错误。因此,必须实施回退策略以捕获应用中所有未捕获的异常。

可以这样做:

还可以确定发生的错误是标准错误还是自定义操作错误。根据结果,可以退出进程并重新启动它以避免意外行为。

与异常不同的是,promise 拒绝不会抛出错误。因此,一个被拒绝的 promise 可能只是一个警告,这让应用有可能遇到意外行为。因此,实现处理 promise 拒绝的回退机制至关重要。

可以这样做:

  • https://www.valentinog.com/blog/error/
  • https://kinsta.com/blog/errors-in-javascript/
  • https://blog.bitsrc.io/javascript-exception-handling-patterns-best-practices-f7d6fcab735d

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。