通过 Typescript class 继承 Error 实现自定义错误类型并编译到 ES5 时,遇到了一个坑。
|  | 
compilerOptions.target 设为 "es5"。
但是运行起来:
|  | 
原因
使用 Babel/Typescript 编译出的代码有类似的问题
Typescript 2.7.2 编译出的代码
|  | 
原因在于,Error 是一个特殊的存在,即是一个构造函数,也是一个普通函数。以下两种调用皆可返回 error object。
|  | 
那么在调用以下函数时,_super 为 Error,返回的即是 Error(this, arguments),而不是 this 。
|  | 
在 Typescript 中
翻了翻文档,Typescript 2.1 的一些 breaking change 导致对于一些原生对象 (Error/Array/Map) 的继承无法正常工作,应该就是由 generated code 的改变造成的。官方给出的一个建议是:
|  | 
但是这个写法其实相当的傻,因为对于每一个子类的构建函数来说,在改变原型之前,是无法拿到正确的子类实例'this.constructor' 的,所以 Object.setPrototypeOf 需要出现在所有子类的构建函数中。
解决方案
只好把原型继承拿回来了,最终在 target 为 es5 及以下的解决方案:
|  | 
构建一个中间的辅助类,并不直接采用 class 继承 Error,而只实现 Error 接口,采用原型继承,此类的示例可经过 instanceOf 的检验。通过 Error.captureStackTrace 在初始化此类实例时能够捕获调用栈。
|  | 
如果编译目标为 ES6 以上呢?
此时编译器就不需要去帮你转化 class 的实现了,会把你的代码原样输出:
** 但是!** 之前的解决方案在 nodejs 中运行会报错:
|  | 
因为使用 class 关键字声明的 ExtensibleError 是一个叫做类构造器(class constructor)的特殊函数,它的 prototype 是只读的,试图去改变它的话,只有报错(nodejs)和不生效两种可能。
以下的代码在现在的 chrome(V8) 和 firefox(SpiderMonkey) 引擎中执行结果都是一样。
|  | 
如果 target 是 ES6 以上的,简简单单写 class ExtensibleError extends Error {} 就行了。
同时我们的解决方案为了在不同编译 target 下都能正常工作,可以加入一个运行时的检测。
|  | 
参考
