js
import defined from "./defined.js";
/**
* 构造一个由于开发者错误而抛出的异常对象,例如参数无效、
* 参数超出范围等。该异常只应在开发阶段抛出;
* 通常表示调用方代码中存在 bug。这个异常不应该被
* 捕获;调用方代码应尽量避免触发它。
* <br /><br />
* 另一方面,{@link RuntimeError} 表示可能在运行时抛出的异常,
* 例如内存不足,此时调用方代码应做好捕获处理。
*
* @alias DeveloperError
* @constructor
* @extends Error
*
* @param {string} [message] 该异常的错误消息。
*
* @see RuntimeError
*/
function DeveloperError(message) {
/**
* 'DeveloperError' 表示该异常由开发者错误导致。
* @type {string}
* @readonly
*/
this.name = "DeveloperError";
/**
* 对异常被抛出原因的说明。
* @type {string}
* @readonly
*/
this.message = message;
// 在 IE 等浏览器中,只有真正抛出错误后才会存在 stack 属性。
let stack;
try {
throw new Error();
} catch (e) {
stack = e.stack;
}
/**
* 此异常的堆栈跟踪(如果可用)。
* @type {string}
* @readonly
*/
this.stack = stack;
}
if (defined(Object.create)) {
DeveloperError.prototype = Object.create(Error.prototype);
DeveloperError.prototype.constructor = DeveloperError;
}
DeveloperError.prototype.toString = function() {
let str = `${this.name}: ${this.message}`;
if (defined(this.stack)) {
str += `\n${this.stack.toString()}`;
}
return str;
};
/**
* 私有方法。
* @private
*/
DeveloperError.throwInstantiationError = function() {
throw new DeveloperError(
"This function defines an interface and should not be called directly.",
);
};
export default DeveloperError;
这段代码定义了一个自定义错误类型 DeveloperError,用于表示由开发者错误 导致的异常(如参数无效、调用方式错误等)。它与常规的 Error 类似,但更清晰地表达了异常的性质,帮助开发者在调试阶段快速定位问题。
整体结构
-
导入依赖
jsimport defined from "./defined.js";使用之前定义的
defined工具函数来安全地检查变量是否已定义。 -
构造函数
DeveloperErrorjsfunction DeveloperError(message) { this.name = "DeveloperError"; this.message = message; // 尝试生成堆栈信息 let stack; try { throw new Error(); } catch (e) { stack = e.stack; } this.stack = stack; }- 设置
name和message属性,符合 JavaScript 错误对象的惯例。 - 通过
try...catch创建一个临时错误来获取堆栈信息,这样可以保证即使在旧浏览器(如 IE)中也能获得 stack 属性。 - 最后将堆栈信息赋给实例的
stack属性。
- 设置
-
原型链继承
jsif (defined(Object.create)) { DeveloperError.prototype = Object.create(Error.prototype); DeveloperError.prototype.constructor = DeveloperError; }- 使用
Object.create将DeveloperError.prototype设为Error.prototype的副本,实现继承。 - 同时修正
constructor指向,确保实例的constructor正确指向DeveloperError。 - 检查
Object.create是否存在是为了兼容非常老的环境(如 IE 8 及以下),但实际现代项目中通常不需要。
- 使用
-
自定义
toString方法jsDeveloperError.prototype.toString = function() { let str = `${this.name}: ${this.message}`; if (defined(this.stack)) { str += `\n${this.stack.toString()}`; } return str; };- 输出格式为
"DeveloperError: 错误信息\n堆栈跟踪",与原生Error的toString行为类似但更可控。
- 输出格式为
-
静态辅助方法
jsDeveloperError.throwInstantiationError = function() { throw new DeveloperError( "This function defines an interface and should not be called directly.", ); };- 用于标记某个函数是抽象接口,不应该被直接调用。如果被调用,就会抛出一个
DeveloperError。这是一种简单的接口定义模式。
- 用于标记某个函数是抽象接口,不应该被直接调用。如果被调用,就会抛出一个
设计意图与使用场景
1. 区分错误类型
DeveloperError:表示调用方代码存在 bug,应在开发阶段被避免 。例如传入null参数、调用一个未实现的方法等。这类错误通常不应被try...catch捕获,而是让程序直接崩溃,以便开发者快速修复。RuntimeError(注释中提到但未在此文件实现):表示运行时可能出现的异常(如内存不足、网络错误),调用方应主动捕获并处理。
这种区分符合"防御性编程"原则:将"开发期错误"与"运行期错误"分开处理。
2. 堆栈信息的获取技巧
通过主动 throw new Error() 再捕获来获取堆栈,可以确保在构造函数执行时就能拿到准确的调用堆栈,即使 new DeveloperError() 时没有实际抛出错误。这种写法比直接访问 new Error().stack 更可靠,因为某些环境下 Error 对象的 stack 属性只在抛出时才被填充。
3. 接口约束
throwInstantiationError 方法常用于实现"抽象类"或"接口"模式。例如:
js
class BaseClass {
constructor() {
if (new.target === BaseClass) {
DeveloperError.throwInstantiationError();
}
}
}
当试图直接实例化 BaseClass 时,就会抛出一个清晰的错误,提示不应该直接调用。
注意事项
- 继承兼容性 :代码中检查了
Object.create是否存在,并回退(如果没有则不做原型链设置)。但在现代浏览器中,Object.create普遍存在,因此实际都会执行继承逻辑。 - 堆栈信息 :在不同 JavaScript 引擎中,
stack属性的格式可能不同,但toString方法中调用了this.stack.toString(),确保能转换为字符串。 - 命名与导出 :
DeveloperError作为默认导出,可在其他模块中直接导入使用。
使用示例
js
import DeveloperError from './DeveloperError.js';
function validateParam(value) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
// ...
}
// 或者用于标记抽象方法
class AbstractClass {
abstractMethod() {
DeveloperError.throwInstantiationError();
}
}
总结
DeveloperError 是对原生 Error 的一个轻量级封装,主要目的是明确区分错误的来源(开发者错误 vs 运行时错误),并提供更友好的错误信息输出。它体现了良好的工程实践:使用自定义错误类型来增强代码的可读性和可维护性,同时通过辅助方法简化接口定义。