什么是异常(Exception)?
程序运行过程中,破坏程序正常控制流程的事件。
前端中异常的类型
- Error:基类型;
- ReferenceError:找不到对象时抛出;
- SyntaxError:语法错误;
- TypeError:类型错误时抛出;
- EvalError:eval()函数发生异常时抛出;
- InternalError :js引擎内部发生的错误;
- RangeError:数值超出范围时触发;
- URIError:URI格式不正确时抛出,常发生在encodeURI或者decodeURI调用时;
- DOMException:调用web api属性时发生的异常事件
还有就是css异常,不会抛错阻塞ui线程,但是样式不会生效,所以无伤大雅。在.html文件里,比如标签写错了,没收尾,这些错误也不会阻塞ui线程的渲染。但是在.vue文件里面,因为要过vue-loader,vue-loader编译模板的时候,会强校验html的标签是否闭合。这就跟在.ts文件里,要过ts-loader是一个道理,不过这时候爆出的大都是语法错误,不修复就没法投入生产,只要错误不上线,都好说。
那么,请问以下脚本执行会出错么?抛什么错误呢?
// 片段1:
Let b;
b.a;
// 片段2:
const a = 1;
a =2;
无论是发生在编译过程中、运行时、还是逻辑错误,最后都会在命令行或者浏览器控制台抛出错误详情。
那ECMAScript内建的错误机制是什么?错误抛出时候会怎么处理错误信息呢?我们都知道js引擎是通过上下文栈来存储函数的,先进后出,当错误发生时,就会沿着函数上下文站向上冒泡,直到找到指定的错误代码处理为止,也就是在这时候抛出错误。
而且 js是单线程的,任何异常都会导致进程阻塞,那怎么处理异常,才能让异常不阻塞进程,还能方便修复异常呢?
常见的错误处理方式try-catch
try中的词法作用于:https://www.ecma-international.org/ecma-262/10.0/index.html#sec-try-statement
try-catch适合处理一些无法控制的错误,比如网络请求、各种I/O操作等。将有可能发生错误的代码放在try语句块中,一旦发生异常,就将控制权转交给catch块执行,然后继续执行后续任务,不会阻塞进程。但是try块中的异步、语法错误,catch是捕获不到的,所以需要单独处理一次。Vue中也对异步的错误做了单独处理,这个我们后面再看。
比如下面这段代码中的异步error:
try语句块中setTimeout任务中会抛出错误,但是因为js是基于事件循环机制的,当try-catch运行完之后,才会在循环队列里取出setTimeout任务执行,因此catch是捕获不到异步错误的。
再看这段中的语法error:
不过在开发环境下,语法错误,一般都会被编辑器抛出,并且会导致程序的崩溃,能得到及时的修复。
常见的错误捕获方式window.onerror:
常见的运行时错误捕获方式就是window.onerror了,js运行时,没有被try-catch住的错误,一般会触发window.onerror事件,接受的参数包括错误信息msg、发生错误的脚本url、发生错误的行号row列号col、Error对象,Error里面包含错误堆栈等详细信息。当然,window.onerror事件也是捕获不到语法错误的。
其实window.addEventListener("error", e => {});也可以捕获错误,但是返回的是个ErrorEvent事件对象,错误信息不如onerror事件完整。
常见错误捕获方式window.unhandledrejection
从es6开始,js支持promise对象,允许控制延迟和异步的操作流。Promise有pending、fulfilled、rejected、settled四种状态,前三种大家经常见,第四种是指promise处于fulfilled或者rejected二者的任意一个状态的时候。发生在promise块中的错误,无论是否catch,都会触发unhandledrejection事件。
如上图代码片段所示,window.onerror它其实是捕获不到promise reject抛出的异常的。如果在promise里没有手动catch异常,则该异常就会被漏报。
看完了同步的和异步的,那如果跨域的资源出错,怎么办?
跨域资源出错怎么办?
script Error?当引用第三方script的时候,脚本出现错误,直接提示“script error”。
解决办法1:设置crossorigin=”anonymous”,在非同源情况下,设置 "anonymous" 关键字将不会通过 cookies,客户端 SSL 证书或 HTTP 认证交换用户凭据。但是这个属性并不是所有浏览器都支持(CORS settings attributes - HTML(超文本标记语言))。
解决办法2:将第三方脚本方法放在try-catch里。
vue的errorHandler和errorCaptured
vue主要是通过errorCaptured钩子来捕获错误,通过errorHandler处理错误。
errorCaptured是捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:err错误对象、vm发生错误的组件实例,info一个包含错误来源信息的字符串。此钩子如果返回 false ,该错误句不用继续向上传播。
性能:
在日常开发中,window.onerror主要是捕获意外的错误,而try-catch是监控可能发生的特定错误。其实现有的错误处理工具,比如sentry,都是封装了一层try-catch,那try-catch一定会延长执行栈,但是会不会影响代码的性能呢?
看上面俩例子,都是一千万次求和。不加try-catch的性能和加了try-catch的差距其实并不大。因为chrome v8引进了新的优化引擎TurboFan,优化了try/catch/finally、for...of 等语法,还支持了es6+的一些新语法,所以不用担心try-catch的性能问题。但是也要合理使用,比如,我们看下图第三个示例,将for循环放在catch块之后,性能大幅度下降。
因为V8(https://v8docs.nodesource.com/node-0.8/d4/da0/v8_8h_source.html)引擎更换了新的编译器TurboFan。在 v8 优化之前,前端 try catch 存在挺大的性能问题。
js引擎会拷贝当前的词法环境,拷贝的其实就是当前 scope 下的所有的变量。
引擎V8以及技术优化:https://blog.csdn.net/hgl868/article/details/45095153
提前规避逻辑错误的一个办法--自动化测试:
vue中的应用,详情见:https://vue-test-utils.vuejs.org/
参考:
常见错误类型: https://www.ecma-international.org/ecma-262/10.0/index.html#sec-native-error-types-used-in-this-standard-referenceerror
驳《慎用 try catch》:https://juejin.im/post/5c199882f265da617464c745