TypeScript 中如何继承 Error 类
栏目: JavaScript · 发布时间: 5年前
内容简介:在 JavaScript 中很多时候都需要自定义错误,尤其是开发 Node.js 应用的时候。 比如一个典型的网站服务器可能需要有为了容易讨论最佳实践,首先明确我们自定义的 CustomError 需要做到哪些功能。 下面是 Harttle 的观点:下面举个例子,这是一个
在 JavaScript 中很多时候都需要自定义错误,尤其是开发 Node.js 应用的时候。
比如一个典型的网站服务器可能需要有 NetworkError
, DatabaseError
, UnauthorizedError
等。
我们希望这些类都拥有 Error
的特性:有错误消息、有调用栈、有方便打印的 toString
等。
最直观的实现方式便是 继承 Error 类
。
但考虑 TypeScript 需要编译到 ES5 兼容性问题会较为复杂,
本文用来帮助理解 TypeScript 中继承 Error 的问题来源以及对应的几种解决方式。
我们需要怎样的 CustomError
为了容易讨论最佳实践,首先明确我们自定义的 CustomError 需要做到哪些功能。 下面是 Harttle 的观点:
-
可以调用
new CustomError()
来创建,并且instanceof Error
操作应该返回true
。可以用来创建是基本要求,能够被视为Error
的实例能够兼容既有系统(比如toString()
要返回调用栈),同时符合惯例。 -
.stack
属性首行应为CustomeError: <message>
。如果是Error: <message>
可能就没那么漂亮。 -
.stack
属性应当包含调用栈并指向new CustomError()
的那一行。这一点可能是关键,如果指向CustomError
构造函数中的某一行,就会给这个类的使用方造成困惑。
下面举个例子,这是一个 message
为 "intended"
的 CustomError
的 .stack
属性值:
CustomError: intended at Object.<anonymous> (/Users/harttle/Downloads/bar/a.js:10:13) at Module._compile (module.js:653:30) at Object.Module._extensions..js (module.js:664:10) at Module.load (module.js:566:32) at tryModuleLoad (module.js:506:12) at Function.Module._load (module.js:498:3) at Function.Module.runMain (module.js:694:10) at startup (bootstrap_node.js:204:16) at bootstrap_node.js:625:3
ES5 中如何继承 Error?
Error 是一个特殊的对象,或者说 JavaScript 的 new
是一个奇葩的存在。
为方便后续讨论,我们先讨论组 ES5 时代是怎样继承 Error 的。 我们说 JavaScript 是一门混杂的语言
,如何继承 Error 就是一个典型的例子。
如果你熟悉原型继承的方式,应该会写出如下代码:
function CustomError (message) { Error.call(this, message) } CustomError.prototype = new Error()
因为 stack 只在 new
的时候生成,上述实现不能满足功能 2 和功能 3,也就是说:
-
stack 的第一行是总是
Error
而不是CustomError
且不包含 message 信息。 -
stack 总是指向
new Error()
的那一行,而不是new CustomError()
。
Node 文档
中描述了一个 captureStackTrace
方法来解决这个问题,改动后的实现如下:
function CustomError (msg) { this.name = 'CustomError' this.message = msg Error.captureStackTrace(this, CustomError) } CustomError.prototype = new Error()
其中 .captureStackTrace()
会使用传入对象的 name 和 message 来生成 stack 的前缀;同时第二个参数用来指定在调用栈中忽略掉哪一部分,这样栈就会指向 new CustomError
的地方而不是 captureStackTrace()
的地方。
ES6 中如何继承 Error?
既然 ES6 通过 class
和 extends
等关键字给出了类继承机制,
那么想必通过编写 CustomError
类来继承 Error
。事实也确实如此,只需要在构造函数中调用父类构造函数并赋值 name
即可实现文章开始提到的三个功能:
class CustomError extends Error { constructor(msg) { super(msg) this.name = 'CustomError' } }
TypeScript 中如何继承 Error?
ES6 中提供了 new.target
属性,
使得 Error
的构造函数中可以获取 CustomError
的信息,以完成原型链的调整。
因此 TypeScript 需要编译到 ES5 时上述功能仍然是无法自动实现。
在 TypeScript 中的体现是形如上述 ES6 的代码片段会被编译成:
var CustomError = /** @class */ (function (_super) { __extends(CustomError, _super); function CustomError(msg) { var _this = _super.call(this, msg) || this; _this.name = 'CustomError'; return _this; } return CustomError; }(Error));
注意 var _this = _super.call(this, msg) || this;
中 this 被替换掉了
。
在 TypeScript 2.1 的 changelog
中描述了这个 Breaking Change。
**这会造成 CustomError
的所有对象方法都无法使用,这里介绍几种 workaround:
题外话,这个分支可能会导致测试覆盖率中的 分支未覆盖问题 。可以只在 ES6 下产生测试覆盖报告来解决。
1. 使用 setPrototypeOf 还原原型链
这是 TypeScript 官方给出的解决方法,见 这里 。
class CustomError extends Error { constructor(message) { super(message); Object.setPrototypeOf(this, FooError.prototype); } }
注意这是一个性能很差的方法,且在 ES6 中提出,兼容性也很差。在不兼容的环境下可以使用 __proto__
来替代。
更多原型链的解释可以参考 JavaScript 内置对象与原型链结构
。
2. 坚持使用 ES5 的方式
不使用 ES6 特性,仍然使用本文前面介绍的 『ES5 中如何继承 Error?』给出的方法。
3. 限制对象方法的使用
虽然 CustomError
的对象函数无法使用,但
CustomError
仍然支持 protected 级别的方法供子类使用,阉割的地方在于自己不能调用。
由于 JavaScript 中对象属性必须在构造函数内赋值,因此 对象属性也不会受到影响
。也就是说:
class CustomError extends Error { count: number = 0 constructor(msg) { super(msg) this.count // OK,属性不受影响 this.print() // TypeError: _this.print is not a function,因为 this 被替换了 } print() { console.log(this.stack) } } class DerivedError extends CustomError { constructor(msg) { super(msg) super.print() // OK,因为 print 是直接从父类原型获取的,即 `_super.prototype.print` } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 028.Python面向对象继承(单继承,多继承,super,菱形继承)
- PHP类继承、接口继承关系概述
- 面向对象:理解 Python 类的单继承与多继承
- java入门第二季--继承--java中的继承初始化顺序
- 前端基本功(七):javascript中的继承(原型、原型链、继承的实现方式)
- 组合还是继承,这是一个问题?——由模式谈面向对象的原则之多用组合、少用继承
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Linux内核完全剖析
赵炯 / 机械工业出版社 / 2006-1 / 79.00元
本书对早期Linux操作系统内核全部代友文件进行了详细的剖析,旨在让读者在尽量短的时间内对Linux的工作机理获得全面而深刻的理解,为进一步学习和研究Linux系统打下坚实的基础。虽然选择的版本较低,但该内核已能够正常编译运行,并且其中已包括了Linux工作原理的精髓。书中首先以Linux源代码版本的变迁为主线,简要介绍了Lin-ux系统的发展历史,同时着重说明了各个内核版本之间的主要区别和改进方......一起来看看 《Linux内核完全剖析》 这本书的介绍吧!