简单了解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 相关文章推荐
JScript中的&quot;this&quot;关键字使用方式补充材料
Mar 08 Javascript
Javascript 陷阱 window全局对象
Nov 26 Javascript
文本框输入时 实现自动提示(像百度、google一样)
Apr 05 Javascript
jQuery JSON实现无刷新三级联动实例探讨
May 28 Javascript
js实现动画特效的文字链接鼠标悬停提示的方法
Mar 02 Javascript
JavaScript 实现完美兼容多浏览器的复制功能代码
Apr 28 Javascript
在JavaScript应用中使用RequireJS来实现延迟加载
Jul 01 Javascript
学习JavaScript设计模式(封装)
Nov 26 Javascript
微信小程序五子棋游戏AI实现方法【附demo源码下载】
Feb 20 Javascript
详解vue2.6插槽更新v-slot用法总结
Mar 09 Javascript
Vue数据驱动表单渲染,轻松搞定form表单
Jul 19 Javascript
如何开发一个渐进式Web应用程序PWA
May 10 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
在windows服务器开启php的gd库phpinfo中未发现
2013/01/13 PHP
ThinkPHP发送邮件示例代码
2016/10/08 PHP
PHP安装扩展mcrypt以及相关依赖项深入讲解
2021/03/04 PHP
前端开发部分总结[兼容性、DOM操作、跨域等](持续更新)
2010/03/04 Javascript
js类型转换与引用类型详解(Boolean_Number_String)
2014/03/07 Javascript
Jquery动态替换div内容及动态展示的方法
2015/01/23 Javascript
javascript实现点击单选按钮链接转向对应网址的方法
2015/08/12 Javascript
Jquery幻灯片特效代码分享--打开页面随机选择切换方式(3)
2015/08/15 Javascript
JS实现带有3D立体感的银灰色竖排折叠菜单代码
2015/10/20 Javascript
node.js基于mongodb的搜索分页示例
2017/01/22 Javascript
koa-router路由参数和前端路由的结合详解
2019/05/19 Javascript
JS实现网站吸顶条
2020/01/08 Javascript
[15:35]教你分分钟做大人:天怒法师
2014/10/30 DOTA
[00:32]2018DOTA2亚洲邀请赛出场——LGD
2018/04/04 DOTA
[49:08]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第一场 11.27
2020/12/01 DOTA
python 文件与目录操作
2008/12/24 Python
DJANGO-ALLAUTH社交用户系统的安装配置
2014/11/18 Python
Python中的左斜杠、右斜杠(正斜杠和反斜杠)
2016/08/30 Python
使用Python对SQLite数据库操作
2017/04/06 Python
Python内置方法实现字符串的秘钥加解密(推荐)
2019/12/09 Python
浅谈python之自动化运维(Paramiko)
2020/01/31 Python
python实现最速下降法
2020/03/24 Python
手把手教你安装Windows版本的Tensorflow
2020/03/26 Python
idea2020手动安装python插件的实现方法
2020/07/17 Python
python爬虫工具例举说明
2020/11/30 Python
html5中如何将图片的绝对路径转换成文件对象
2018/01/11 HTML / CSS
美国手工艺品市场的领导者:Annie’s
2019/04/04 全球购物
建筑专业毕业生推荐信
2013/11/21 职场文书
六十大寿答谢词
2014/01/12 职场文书
男方父母婚礼答谢词
2014/01/25 职场文书
学习雷锋做美德少年寄语大全
2014/04/09 职场文书
导师就业推荐信范文
2014/05/22 职场文书
2014年志愿者工作总结
2014/11/20 职场文书
2015年全国爱眼日活动小结
2015/02/27 职场文书
旅游安全责任协议书
2016/03/22 职场文书
公文写作:新员工转正申请书范本3篇!
2019/08/07 职场文书