前言
在JavaScript 中很多時(shí)候都需要自定義錯(cuò)誤,尤其是開發(fā) Node.js 應(yīng)用的時(shí)候。 比如一個(gè)典型的網(wǎng)站服務(wù)器可能需要有 NetworkError, DatabaseError, UnauthorizedError 等。 我們希望這些類都擁有 Error 的特性:有錯(cuò)誤消息、有調(diào)用棧、有方便打印的 toString 等。 最直觀的實(shí)現(xiàn)方式便是 繼承 Error 類。 但考慮 TypeScript 需要編譯到 ES5 兼容性問題會較為復(fù)雜, 本文用來幫助理解 TypeScript 中繼承 Error 的問題來源以及對應(yīng)的幾種解決方式。
我們需要怎樣的 CustomError
為了容易討論最佳實(shí)踐,首先明確我們自定義的 CustomError 需要做到哪些功能。 下面是 Harttle 的觀點(diǎn):
下面舉個(gè)例子,這是一個(gè) message 為 "intended" 的 CustomError 的 .stack 屬性值:
CustomError: intendedat 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è)特殊的對象,或者說 JavaScript 的 new 是一個(gè)奇葩的存在。 為方便后續(xù)討論,我們先討論組 ES5 時(shí)代是怎樣繼承 Error 的。 我們說 JavaScript 是一門混雜的語言,如何繼承 Error 就是一個(gè)典型的例子。 如果你熟悉 原型繼承的方式,應(yīng)該會寫出如下代碼:
function CustomError (message) {Error.call(this, message)}CustomError.prototype = new Error()
因?yàn)?stack 只在 new 的時(shí)候生成,上述實(shí)現(xiàn)不能滿足功能 2 和功能 3,也就是說:
Node 文檔 中描述了一個(gè) captureStackTrace 方法來解決這個(gè)問題,改動后的實(shí)現(xiàn)如下:
function CustomError (msg) {this.name = 'CustomError'this.message = msgError.captureStackTrace(this, CustomError)}CustomError.prototype = new Error()
其中 .captureStackTrace() 會使用傳入對象的 name 和 message 來生成 stack 的前綴;同時(shí)第二個(gè)參數(shù)用來指定在調(diào)用棧中忽略掉哪一部分,這樣棧就會指向 new CustomError 的地方而不是 captureStackTrace() 的地方。
ES6 中如何繼承 Error?
既然 ES6 通過 class 和 extends 等關(guān)鍵字給出了類繼承機(jī)制, 那么想必通過編寫 CustomError 類來繼承 Error。事實(shí)也確實(shí)如此,只需要在構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù)并賦值 name 即可實(shí)現(xiàn)文章開始提到的三個(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í)上述功能仍然是無法自動實(shí)現(xiàn)。 在 TypeScript 中的體現(xiàn)是形如上述 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 中描述了這個(gè) Breaking Change。 **這會造成 CustomError 的所有對象方法都無法使用,這里介紹幾種 workaround:
題外話,這個(gè)分支可能會導(dǎo)致測試覆蓋率中的 分支未覆蓋問題??梢灾辉?ES6 下產(chǎn)生測試覆蓋報(bào)告來解決。
1. 使用 setPrototypeOf 還原原型鏈
這是 TypeScript 官方給出的解決方法,見這里。
class CustomError extends Error {constructor(message) {super(message);Object.setPrototypeOf(this, FooError.prototype);}}
注意這是一個(gè)性能很差的方法,且在 ES6 中提出,兼容性也很差。在不兼容的環(huán)境下可以使用 __proto__ 來替代。
2. 堅(jiān)持使用 ES5 的方式
不使用 ES6 特性,仍然使用本文前面介紹的 『ES5 中如何繼承 Error?』給出的方法。
3. 限制對象方法的使用
雖然 CustomError 的對象函數(shù)無法使用,但 CustomError 仍然支持 protected 級別的方法供子類使用,閹割的地方在于自己不能調(diào)用。 由于 JavaScript 中對象屬性必須在構(gòu)造函數(shù)內(nèi)賦值,因此對象屬性也不會受到影響。也就是說:
class CustomError extends Error {count: number = 0constructor(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 是直接從父類原型獲取的,即 `_super.prototype.print`}}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持VeVb武林網(wǎng)。
新聞熱點(diǎn)
疑難解答