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
}
}