APP下载
报价宝  ›  科技  › 

TypeScript 中如何继承 Error 类

报价宝 来源:baojiabao.com 发布时间:2019-10-31 21:03:00 11月14日更新
报价宝综合消息TypeScript 中如何继承 Error 类

在 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: 。如果是 Error: 可能就没那么漂亮。.stack 属性应当包含呼叫栈并指向 new CustomError() 的那一行。这一点可能是关键,如果指向 CustomError 建构函式中的某一行,就会给这个类的使用方造成困惑。下面举个例子,这是一个 message 为 "intended" 的 CustomError 的 .stack 属性值:

CustomError: intended

at Object. (/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`

}

}

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可学识粗浅写作仓促,如有错误辛苦评论或私信指出

有想学习前端的朋友欢迎私信“学习“,领取前端开发全套学习资料

文章标签: 报价宝 降噪耳机价格 耳机价格 红米手机价格 华为手机价格 小米手机价格 电视机价格 笔记本电脑价格 笔记本价格 汽车价格 报价宝 笔记本电脑价格 小米手机价格 红米手机价格 降噪耳机价格