簡(jiǎn)單了解TypeScript中如何繼承 Error 類(lèi)
前言
在JavaScript 中很多時(shí)候都需要自定義錯(cuò)誤,尤其是開(kāi)發(fā) Node.js 應(yīng)用的時(shí)候。 比如一個(gè)典型的網(wǎng)站服務(wù)器可能需要有 NetworkError, DatabaseError, UnauthorizedError 等。 我們希望這些類(lèi)都擁有 Error 的特性:有錯(cuò)誤消息、有調(diào)用棧、有方便打印的 toString 等。 最直觀的實(shí)現(xiàn)方式便是 繼承 Error 類(lèi)。 但考慮 TypeScript 需要編譯到 ES5 兼容性問(wèn)題會(huì)較為復(fù)雜, 本文用來(lái)幫助理解 TypeScript 中繼承 Error 的問(wèn)題來(lái)源以及對(duì)應(yīng)的幾種解決方式。
我們需要怎樣的 CustomError
為了容易討論最佳實(shí)踐,首先明確我們自定義的 CustomError 需要做到哪些功能。 下面是 Harttle 的觀點(diǎn):
- 可以調(diào)用 new CustomError() 來(lái)創(chuàng)建,并且 instanceof Error 操作應(yīng)該返回 true。可以用來(lái)創(chuàng)建是基本要求,能夠被視為 Error 的實(shí)例能夠兼容既有系統(tǒng)(比如 toString() 要返回調(diào)用棧),同時(shí)符合慣例。
- .stack 屬性首行應(yīng)為 CustomeError: <message>。如果是 Error: <message> 可能就沒(méi)那么漂亮。
- .stack 屬性應(yīng)當(dāng)包含調(diào)用棧并指向 new CustomError() 的那一行。這一點(diǎn)可能是關(guān)鍵,如果指向 CustomError 構(gòu)造函數(shù)中的某一行,就會(huì)給這個(gè)類(lèi)的使用方造成困惑。
下面舉個(gè)例子,這是一個(gè) 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 是一個(gè)特殊的對(duì)象,或者說(shuō) JavaScript 的 new 是一個(gè)奇葩的存在。 為方便后續(xù)討論,我們先討論組 ES5 時(shí)代是怎樣繼承 Error 的。 我們說(shuō) JavaScript 是一門(mén)混雜的語(yǔ)言,如何繼承 Error 就是一個(gè)典型的例子。 如果你熟悉 原型繼承的方式,應(yīng)該會(huì)寫(xiě)出如下代碼:
function CustomError (message) { Error.call(this, message) } CustomError.prototype = new Error()
因?yàn)?stack 只在 new 的時(shí)候生成,上述實(shí)現(xiàn)不能滿足功能 2 和功能 3,也就是說(shuō):
- stack 的第一行是總是 Error 而不是 CustomError 且不包含 message 信息。
- stack 總是指向 new Error() 的那一行,而不是 new CustomError()。
Node 文檔 中描述了一個(gè) captureStackTrace 方法來(lái)解決這個(gè)問(wèn)題,改動(dòng)后的實(shí)現(xiàn)如下:
function CustomError (msg) { this.name = 'CustomError' this.message = msg Error.captureStackTrace(this, CustomError) } CustomError.prototype = new Error()
其中 .captureStackTrace() 會(huì)使用傳入對(duì)象的 name 和 message 來(lái)生成 stack 的前綴;同時(shí)第二個(gè)參數(shù)用來(lái)指定在調(diào)用棧中忽略掉哪一部分,這樣棧就會(huì)指向 new CustomError 的地方而不是 captureStackTrace() 的地方。
ES6 中如何繼承 Error?
既然 ES6 通過(guò) class 和 extends 等關(guān)鍵字給出了類(lèi)繼承機(jī)制, 那么想必通過(guò)編寫(xiě) CustomError 類(lèi)來(lái)繼承 Error。事實(shí)也確實(shí)如此,只需要在構(gòu)造函數(shù)中調(diào)用父類(lèi)構(gòu)造函數(shù)并賦值 name 即可實(shí)現(xiàn)文章開(kāi)始提到的三個(gè)功能:
class CustomError extends Error { constructor(msg) { super(msg) this.name = 'CustomError' } }
TypeScript 中如何繼承 Error?
ES6 中提供了 new.target 屬性, 使得 Error 的構(gòu)造函數(shù)中可以獲取 CustomError 的信息,以完成原型鏈的調(diào)整。 因此 TypeScript 需要編譯到 ES5 時(shí)上述功能仍然是無(wú)法自動(dòng)實(shí)現(xiàn)。 在 TypeScript 中的體現(xiàn)是形如上述 ES6 的代碼片段會(huì)被編譯成:
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 中描述了這個(gè) Breaking Change。 **這會(huì)造成 CustomError 的所有對(duì)象方法都無(wú)法使用,這里介紹幾種 workaround:
題外話,這個(gè)分支可能會(huì)導(dǎo)致測(cè)試覆蓋率中的 分支未覆蓋問(wèn)題??梢灾辉?ES6 下產(chǎn)生測(cè)試覆蓋報(bào)告來(lái)解決。
1. 使用 setPrototypeOf 還原原型鏈
這是 TypeScript 官方給出的解決方法,見(jiàn)這里。
class CustomError extends Error { constructor(message) { super(message); Object.setPrototypeOf(this, FooError.prototype); } }
注意這是一個(gè)性能很差的方法,且在 ES6 中提出,兼容性也很差。在不兼容的環(huán)境下可以使用 __proto__ 來(lái)替代。
2. 堅(jiān)持使用 ES5 的方式
不使用 ES6 特性,仍然使用本文前面介紹的 『ES5 中如何繼承 Error?』給出的方法。
3. 限制對(duì)象方法的使用
雖然 CustomError 的對(duì)象函數(shù)無(wú)法使用,但 CustomError 仍然支持 protected 級(jí)別的方法供子類(lèi)使用,閹割的地方在于自己不能調(diào)用。 由于 JavaScript 中對(duì)象屬性必須在構(gòu)造函數(shù)內(nèi)賦值,因此對(duì)象屬性也不會(huì)受到影響。也就是說(shuō):
class CustomError extends Error { count: number = 0 constructor(msg) { super(msg) this.count // OK,屬性不受影響 this.print() // TypeError: _this.print is not a function,因?yàn)?this 被替換了 } print() { console.log(this.stack) } } class DerivedError extends CustomError { constructor(msg) { super(msg) super.print() // OK,因?yàn)?print 是直接從父類(lèi)原型獲取的,即 `_super.prototype.print` } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JS添加刪除一組文本框并對(duì)輸入信息加以驗(yàn)證判斷其正確性
需要添加幾組數(shù)據(jù)到數(shù)據(jù)庫(kù),但是具體幾組數(shù)據(jù)不確定,有客戶來(lái)填寫(xiě),所以,這里我用JS進(jìn)行添加刪除子方案,并要對(duì)方案輸入的正確性加以判斷,感興趣的朋友可以了解下2013-04-04jquery的$getjson調(diào)用并獲取遠(yuǎn)程的JSON字符串問(wèn)題
jQuery中常用getJSON來(lái)調(diào)用并獲取遠(yuǎn)程的JSON字符串,將其轉(zhuǎn)換為JSON對(duì)象,如果成功,則執(zhí)行回調(diào)函數(shù),本文將詳細(xì)介紹,需要的朋友可以參考下2012-12-12js實(shí)現(xiàn)文本框中焦點(diǎn)在最后位置
本篇文章主要是對(duì)js實(shí)現(xiàn)文本框中焦點(diǎn)在最后位置的示例代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所 幫助2014-03-03JS如何通過(guò)視頻鏈接獲取視頻時(shí)長(zhǎng)
這篇文章主要介紹了JS如何通過(guò)視頻鏈接獲取視頻時(shí)長(zhǎng),這個(gè)函數(shù)用提供的URL創(chuàng)建一個(gè)新的Video元素,并在loadedmetadata事件被觸發(fā)時(shí)解析一個(gè)帶有視頻持續(xù)時(shí)間的Promise,感興趣的朋友跟隨小編一起看看吧2024-06-06JavaScript擴(kuò)展運(yùn)算符的學(xué)習(xí)及應(yīng)用詳情(ES6)
這篇文章主要介紹了JavaScript擴(kuò)展運(yùn)算符的學(xué)習(xí)及應(yīng)用詳情(ES6),擴(kuò)展運(yùn)算符是ES6新增的一種運(yùn)算符,他可以幫助我們簡(jiǎn)化代碼,簡(jiǎn)化操作,具體相關(guān)知識(shí)感興趣的小伙伴可以查看下面文章的簡(jiǎn)單介紹2022-08-08最全的JavaScript開(kāi)發(fā)工具列表 總有一款適合你
最全的JavaScript開(kāi)發(fā)工具列表分享給你,總有一款適合你!2017-06-06js中獲取鍵盤(pán)事件的簡(jiǎn)單實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇js中獲取鍵盤(pán)事件的簡(jiǎn)單實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10