Source/Core/DeveloperError.js

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 类似,但更清晰地表达了异常的性质,帮助开发者在调试阶段快速定位问题。


整体结构

  1. 导入依赖

    js 复制代码
    import defined from "./defined.js";

    使用之前定义的 defined 工具函数来安全地检查变量是否已定义。

  2. 构造函数 DeveloperError

    js 复制代码
    function DeveloperError(message) {
      this.name = "DeveloperError";
      this.message = message;
      // 尝试生成堆栈信息
      let stack;
      try {
        throw new Error();
      } catch (e) {
        stack = e.stack;
      }
      this.stack = stack;
    }
    • 设置 namemessage 属性,符合 JavaScript 错误对象的惯例。
    • 通过 try...catch 创建一个临时错误来获取堆栈信息,这样可以保证即使在旧浏览器(如 IE)中也能获得 stack 属性。
    • 最后将堆栈信息赋给实例的 stack 属性。
  3. 原型链继承

    js 复制代码
    if (defined(Object.create)) {
      DeveloperError.prototype = Object.create(Error.prototype);
      DeveloperError.prototype.constructor = DeveloperError;
    }
    • 使用 Object.createDeveloperError.prototype 设为 Error.prototype 的副本,实现继承。
    • 同时修正 constructor 指向,确保实例的 constructor 正确指向 DeveloperError
    • 检查 Object.create 是否存在是为了兼容非常老的环境(如 IE 8 及以下),但实际现代项目中通常不需要。
  4. 自定义 toString 方法

    js 复制代码
    DeveloperError.prototype.toString = function() {
      let str = `${this.name}: ${this.message}`;
      if (defined(this.stack)) {
        str += `\n${this.stack.toString()}`;
      }
      return str;
    };
    • 输出格式为 "DeveloperError: 错误信息\n堆栈跟踪",与原生 ErrortoString 行为类似但更可控。
  5. 静态辅助方法

    js 复制代码
    DeveloperError.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 运行时错误),并提供更友好的错误信息输出。它体现了良好的工程实践:使用自定义错误类型来增强代码的可读性和可维护性,同时通过辅助方法简化接口定义。

相关推荐
小璐资源网3 小时前
Java 21 新特性实战:虚拟线程详解
java·开发语言·python
m0_569881473 小时前
基于C++的数据库连接池
开发语言·c++·算法
.select.3 小时前
c++ auto
开发语言·c++·算法
2401_884563243 小时前
C++中的访问者模式高级应用
开发语言·c++·算法
消失的旧时光-19433 小时前
C++ 多态核心三件套:虚函数、纯虚函数、虚析构函数(面试 + 工程完全指南)
开发语言·c++·面试·虚函数·纯虚函数·虚析构函数
青春易逝丶4 小时前
策略模式
java·开发语言·策略模式
freexyn4 小时前
Matlab入门自学七十四:坐标系转换,直角坐标、极坐标和球坐标的转换
开发语言·算法·matlab
weixin_462901974 小时前
ESP32 LED控制代码解析
javascript
小张会进步4 小时前
数组:二维数组
java·javascript·算法