简单了解TypeScript中如何继承 Error 类


Posted in Javascript onJune 21, 2019

前言

在JavaScript 中很多时候都需要自定义错误,尤其是开发 Node.js 应用的时候。 比如一个典型的网站服务器可能需要有 NetworkError, DatabaseError, UnauthorizedError 等。 我们希望这些类都拥有 Error 的特性:有错误消息、有调用栈、有方便打印的 toString 等。 最直观的实现方式便是 继承 Error 类。 但考虑 TypeScript 需要编译到 ES5 兼容性问题会较为复杂, 本文用来帮助理解 TypeScript 中继承 Error 的问题来源以及对应的几种解决方式。

我们需要怎样的 CustomError

为了容易讨论最佳实践,首先明确我们自定义的 CustomError 需要做到哪些功能。 下面是 Harttle 的观点:

  1. 可以调用 new CustomError() 来创建,并且 instanceof Error 操作应该返回 true。可以用来创建是基本要求,能够被视为 Error 的实例能够兼容既有系统(比如 toString() 要返回调用栈),同时符合惯例。
  2. .stack 属性首行应为 CustomeError: <message>。如果是 Error: <message> 可能就没那么漂亮。
  3. .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__ 来替代。

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
如何用javascript控制上传文件的大小
Oct 26 Javascript
Javascript Cookie读写删除操作的函数
Mar 02 Javascript
js获取当前select 元素值的代码
Apr 19 Javascript
JS中for循序中延迟加载动态效果的具体实现
Aug 18 Javascript
jQuery遍历之next()、nextAll()方法使用实例
Nov 08 Javascript
javascript实现列表滚动的方法
Jul 30 Javascript
利用Node.js制作爬取大众点评的爬虫
Sep 22 Javascript
搭建Bootstrap离线文档的方法
Dec 02 Javascript
JS获取多维数组中相同键的值实现方法示例
Jan 06 Javascript
详解vuex中action何时完成以及如何正确调用dispatch的思考
Jan 21 Javascript
JavaScript获取当前url路径过程解析
Dec 27 Javascript
详解Vue中的MVVM原理和实现方法
Jul 15 Javascript
如何在JavaScript中谨慎使用代码注释
Jun 21 #Javascript
简单了解JavaScript中常见的反模式
Jun 21 #Javascript
通过图带你深入了解vue的响应式原理
Jun 21 #Javascript
10种JavaScript最常见的错误(小结)
Jun 21 #Javascript
微信小程序开发注意指南和优化实践(小结)
Jun 21 #Javascript
使用Vue开发自己的Chrome扩展程序过程详解
Jun 21 #Javascript
如何测量vue应用运行时的性能
Jun 21 #Javascript
You might like
复杂检索数据并分页显示的处理方法
2006/10/09 PHP
php 什么是PEAR?
2009/03/19 PHP
ThinkPHP的L方法使用简介
2014/06/18 PHP
thinkPHP5.1框架使用SemanticUI实现分页功能示例
2019/08/03 PHP
javascript控制frame,iframe的src属性代码
2009/12/31 Javascript
jquery+json实现的搜索加分页效果
2010/03/31 Javascript
千分位数字格式化(用逗号隔开 代码已做了修改 支持0-9位逗号隔开)的JS代码
2013/12/05 Javascript
jQuery插件scroll实现无缝滚动效果
2015/04/27 Javascript
js实现带圆角的多级下拉菜单效果
2015/08/28 Javascript
轻松5句话解决JavaScript的作用域
2016/07/15 Javascript
JavaScript实现图片懒加载(Lazyload)
2016/11/28 Javascript
基于JavaScript实现购物车功能
2017/02/07 Javascript
Vue实现自带的过滤器实例
2017/03/09 Javascript
jQuery EasyUI Layout实现tabs标签的实例
2017/09/26 jQuery
JS实现的按钮点击颜色切换功能示例
2017/10/19 Javascript
Vue filter介绍及其使用详解
2017/10/21 Javascript
vue2.0 使用element-ui里的upload组件实现图片预览效果方法
2018/09/04 Javascript
vue百度地图 + 定位的详解
2019/05/13 Javascript
简单了解Javscript中兄弟ifream的方法调用
2019/06/17 Javascript
微信小程序利用Canvas绘制图片和竖排文字详解
2019/06/25 Javascript
JS实现提示效果弹出及延迟隐藏的功能
2019/08/26 Javascript
python操作MongoDB基础知识
2013/11/01 Python
python实现爬虫下载美女图片
2015/07/14 Python
python递归查询菜单并转换成json实例
2017/03/27 Python
pygame游戏之旅 添加键盘按键的方法
2018/11/20 Python
对python产生随机的二维数组实例详解
2018/12/13 Python
django框架模板中定义变量(set variable in django template)的方法分析
2019/06/24 Python
使用pyecharts生成Echarts网页的实例
2019/08/12 Python
python3 反射的四种基本方法解析
2019/08/26 Python
Hanky Panky官方网站:内衣和睡衣
2019/07/25 全球购物
Vans澳大利亚官网:购买鞋子、服装及配件
2019/09/05 全球购物
Chupi官网:在爱尔兰手工制作的订婚、结婚戒指和精美珠宝
2020/09/28 全球购物
各营销点岗位职责范本
2014/03/05 职场文书
银行资信证明
2015/06/17 职场文书
SpringBoot实现异步事件驱动的方法
2021/06/28 Java/Android
云服务器部署 Web 项目的实现步骤
2022/06/28 Servers