JavaScript Error继承踩坑记
Error ES6 Class 继承
在 Web App 中,我们通常会创建自定义错误类来区分错误类型。如果使用 ES6 的 Class 语法,那么应该有类似如下写法:
class MyError extends Error {
constructor (msg) {
super(msg)
this.message = msg
this.name = "MyError"
}
}
现在我们需要报一个自定义错误,那么有:
var err = new MyError("this is a error message")
同时,在流程处理中,可能需要通过错误类型执行不同的处理逻辑代码:
if (err instanceof MyError) {
// do some job
console.log("MyError occurred")
}
目前看来一切都是那么顺其自然,那么直接去现代浏览器的 Console 或者 Node Repl 环境中执行这段代码,能够正常打印MyError occurred
。
Babel/TypeScript 编译 ES5
目前情况下,ES6 代码应该由 Babel(如果是 TS 用 TypeScript)编译成 ES5,这时会发现错误流程永远触发不了。例如:
var err = new MyError("this is a MyError")
console.log(err instanceof MyError)
输出
> false
而且若有其他类继承MyError
也将出现一样的结果。
如果拿普通的类来继承,例如:
class Animal {
constructor (name) {
this.name = name
}
}
class Bird extends Animal {
constructor (name) {
super(name)
}
}
var swallow = new Bird('swallow')
console.log(swallow instanceof Bird)
则又会输出:
> true
从上图可以看出,Bird
继承Animal
类的行为是完全正常的,包括原型的引用,而Error
的继承,其子类MyError
实例的 __proto__
错误指向到了Error.prototype
那么来看看 Babel 编译的 ES5 代码如何实现继承的,代码做了一下简化:
var MyError = (function (_super) {
Object.setPrototypeOf(MyError, _super)
function F() {this.constructor = MyError}
F.prototype = _super.prototype
MyError.prototype = new F()
function MyError(msg) {
var _this = _super.call(this, msg) || this;
_this.message = msg;
_this.name = "MyError";
return _this;
}
return MyError;
}(Error));
继承实现的代码方式是一致的,_super
参数换成Error
为什么就不行了呢。
回到Error
这个对象本身,它是 JS 中原生基本对象,它即是一个构造函数,也是一个普通函数。即我们创建一个Error
实例可以通过如下两种方式:
var err1 = new Error('msg')
var err2 = Error('msg')
再回到编译的 ES5 代码,在子类构造函数中,第一行会调用父类构造函数:
var _this = _super.call(this, msg) || this
_this
结果也在构造函数尾部返回,当通过new
操作符实例化子类时返回的实例就是这个_this
的对象。
一个普通的父类构造函数,如Animal
中,我们没有做任何return
输出,作为构造函数使用时,就相当于return this
,而我们又通过call
绑定了this
上下文为子类的this
,那么子类构造函数中返回的就是子类的实例。
对于Error
来说,其设计初衷就是可以作为普通函数调用,也就是其内部实现有return
语句的,返回的正是Error
实例,导致了_this
的结果就是这个Error
实例,并作为子类实例的this
存在,这就导致了MyError
子类的实例原型引用直接对接到Error
上。
// Error构造函数可能的部分实现逻辑
function Error(msg){
if (!(this instanceof Error)) {
return new Error(msg)
}
this.message = msg
// more code
}
如何继承 Error
我们想用 ES6 写代码,然后编译成 ES5 上线。但是不改变 Babel 编译方式的情况下,如何最便捷的实现目的呢?这里我们可以设计一个中间类,来改变构造函数中返回的结果,下面代码是 extensible-error 包中的实现:
class ExtensibleError {
}
function ExtendableErrorBuiltin(){
function ExtendableBuiltin(){
Error.apply(this, arguments);
// Set this.message
Object.defineProperty(this, 'message', {
configurable: true,
enumerable: false,
value: arguments.length ? String(arguments[0]) : ''
})
// Set this.name
Object.defineProperty(this, 'name', {
configurable: true,
enumerable: false,
value: this.constructor.name
})
if (typeof Error.captureStackTrace === 'function') {
// Set this.stack
Error.captureStackTrace(this, this.constructor)
}
}
ExtendableBuiltin.prototype = Object.create(Error.prototype);
Object.setPrototypeOf(ExtendableBuiltin, Error);
return ExtendableBuiltin;
}
ExtensibleError = ExtendableErrorBuiltin(Error)
要创建自定义错误类,可以:
class MyError extends ExtensibleError {
constructor (msg, extra) {
super(msg)
this.extra = extra
}
}
- 原文作者:Touchumind
- 原文链接:https://blog.fedepot.com/post/javascript-error-inherit/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。