通过 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 下都能正常工作,可以加入一个运行时的检测。
|
参考