简单了解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 相关文章推荐
doctype后如何获得body.clientHeight的方法
Jul 11 Javascript
js中将多个语句写成一个语句的两种方法小结
Dec 08 Javascript
javascript:void(0)的问题使用探讨
Apr 10 Javascript
快速掌握WordPress中加载JavaScript脚本的方法
Dec 17 Javascript
bootstrap输入框组代码分享
Jun 07 Javascript
jquery.Jcrop结合JAVA后台实现图片裁剪上传实例
Nov 05 Javascript
JS实现双击内容变为可编辑状态
Mar 03 Javascript
vue模板语法-插值详解
Mar 06 Javascript
vue单个组件实现无限层级多选菜单功能
Apr 10 Javascript
vue 之 .sync 修饰符示例详解
Apr 21 Javascript
深入理解Antd-Select组件的用法
Feb 25 Javascript
MutationObserver在页面水印实现起到的作用详解
Jul 07 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
新版mysql+apache+php Linux安装指南
2006/10/09 PHP
源码分析 Laravel 重复执行同一个队列任务的原因
2017/12/25 PHP
PHP实现的支付宝支付功能示例
2019/03/26 PHP
php实现快速对二维数组某一列进行组装的方法小结
2019/12/04 PHP
jquery随机展示头像代码
2011/12/21 Javascript
extjs4 treepanel动态改变行高度示例
2013/12/17 Javascript
基于NodeJS的前后端分离的思考与实践(五)多终端适配
2014/09/26 NodeJs
跟我学习javascript的全局变量
2015/11/16 Javascript
实例解析jQuery插件EasyUI最常用的表单验证规则
2015/11/29 Javascript
适用于javascript开发者的Processing.js入门教程
2016/02/24 Javascript
非常漂亮的相册集 使用jquery制作相册集
2016/04/28 Javascript
JavaScript数组去重算法实例小结
2018/05/07 Javascript
关于layui 弹出层一闪而过就消失的解决方法
2019/09/09 Javascript
javascript实现点击小图显示大图
2020/11/29 Javascript
python的继承知识点总结
2018/12/10 Python
在Python中调用Ping命令,批量IP的方法
2019/01/26 Python
python实现两个经纬度点之间的距离和方位角的方法
2019/07/05 Python
pycharm配置当鼠标悬停时快速提示方法参数
2019/07/31 Python
详解如何在cmd命令窗口中搭建简单的python开发环境
2019/08/29 Python
你还在@微信官方?聊聊Python生成你想要的微信头像
2019/09/25 Python
pandas数据处理进阶详解
2019/10/11 Python
Python3实现监控新型冠状病毒肺炎疫情的示例代码
2020/02/13 Python
Python使用pdb调试代码的技巧
2020/05/03 Python
html5本地存储之localstorage 、本地数据库、sessionStorage简单使用示例
2014/05/08 HTML / CSS
林清轩官方网站:山茶花润肤油开创者
2016/10/26 全球购物
南京某软件公司的.net面试题
2015/11/30 面试题
介绍一下Ruby中的对象,属性和方法
2012/07/11 面试题
高中毕业自我评价
2014/02/08 职场文书
优秀大学生求职自荐信范文
2014/04/19 职场文书
县委班子四风对照检查材料思想汇报
2014/09/29 职场文书
全陪导游词
2015/02/04 职场文书
离婚案件上诉状
2015/05/23 职场文书
2016年猴年新春致辞
2015/08/01 职场文书
Kubernetes中Deployment的升级与回滚
2022/04/01 Servers
浅谈为什么我的 z-index 又不生效了
2022/07/15 HTML / CSS
PostgreSQL逻辑复制解密原理解析
2022/09/23 PostgreSQL