前端知库

Error.isError 和 instanceof Error 有什么区别?

instanceof Error 只看原型链,跨 iframe 创建的错误会判成 false,伪造原型的普通对象反而判成 true。Error.isError 检查的是 Error 构造函数在对象上创建的内部数据,跨 realm 也能认出真正的错误,伪造的对象认不出来。

参考答案

简答

Error.isError(value) 用来判断一个值是不是真正的错误对象,用法和 Array.isArray 一样,返回 true 或 false。

instanceof Error 只检查 Error.prototype 在不在对象的原型链上,这会出两种错。一是 iframe 里抛出的错误,它的原型链指向 iframe 自己的 Error,在父页面里判断 instanceof Error 得到 false。二是用 Object.create(Error.prototype) 伪造的普通对象,原型链对上了,instanceof 反而得到 true。

Error.isError 检查的是 Error() 构造函数在对象上创建的私有数据,规范里叫 [[ErrorData]] 内部槽。所以跨 iframe 的真错误能认出来,伪造原型的对象认不出来。它适合在 catch 里判断捕获到的值,必要时包装成真正的错误。

要注意兼容性,MDN 把它标为 Limited availability。Chrome 134、Firefox 138 起支持,Safari 正式版还没有提供。老环境可以用 core-js 等 polyfill。

instanceof 为什么会判错

instanceof 的规则只有一条:沿着原型链往上找,看能不能找到 Error.prototype。它不关心这个对象是怎么创建出来的。下面这段代码伪造了一个“错误”:

JavaScript
const fake = Object.create(Error.prototype);
console.log(fake instanceof Error); // true
console.log(Object.prototype.toString.call(fake)); // [object Object]
const tagged = { [Symbol.toStringTag]: 'Error' };
console.log(Object.prototype.toString.call(tagged)); // [object Error]
console.log(tagged instanceof Error); // false

fake 从来没有经过 Error 构造函数,只是原型被设成了 Error.prototype,instanceof 就认它是错误。Object.prototype.toString 能看出它不是,可是这个方法会读 Symbol.toStringTag,tagged 只加了一个标签,就被它当成了 [object Error]。

跨 iframe 的情况正好相反。每个 iframe 有自己的一套全局对象,也就有自己的 Error 构造函数。iframe 里 new Error() 创建的对象,原型链上是 iframe 的 Error.prototype,跟父页面的 Error.prototype 不是同一个对象,所以在父页面里 instanceof Error 是 false。这类“不同的全局环境”在规范里叫 realm,判断类型时的通用问题见《怎样准确判断一个值的类型?typeof、instanceof 和 Object.prototype.toString 各有什么局限?》。

Error.isError 看的是什么

Error() 构造函数创建对象时,会给对象加上一份外面改不了的内部数据。Error.isError 只检查这份数据在不在,MDN 把这叫“品牌检查”,Array.isArray 也是同样的思路。

所以结果是这样的(浏览器代码,不在 Node 里运行):

JavaScript
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const FrameError = iframe.contentWindow.Error;
const error = new FrameError('来自 iframe');

Error.isError(error); // true
error instanceof Error; // false
Error.isError(Object.create(Error.prototype)); // false
Error.isError(new TypeError()); // true
Error.isError('Error'); // false

TypeError、RangeError 这些子类都是通过 Error 的机制创建的,所以都返回 true。DOMException 虽然不是规范上的 Error 子类,MDN 说明它在品牌检查上表现得和 Error 一样,Error.isError(new DOMException()) 也是 true。不过 MDN 的兼容表注明,Safari 预览版对 DOMException 返回的是 false。

实际在哪里用

最常见的是 catch 里。JavaScript 允许 throw 任何值,第三方代码可能抛出字符串或普通对象。统一成真正的错误以后,日志和监控才能稳定拿到 message 和 stack:

JavaScript
try {
  runTask();
} catch (caught) {
  const error = Error.isError(caught) ? caught : new Error(String(caught));
  report(error);
}

在微前端、iframe 嵌入、或者跨 realm 传递错误对象的场景里,instanceof Error 不可靠,用 Error.isError 才能判断准。

常见追问

自定义的 class MyError extends Error 能被识别吗?

能。子类构造函数里调用 super() 时,对象是由 Error 的机制创建的,带有那份内部数据,所以 Error.isError 返回 true。

不支持的环境怎么办?

MDN 列出了 core-js 和 es-shims 的 error.iserror polyfill。

全局错误应该怎样收集?

window.onerror、unhandledrejection 这些入口见《同步错误、Promise 拒绝和全局异常应该怎样处理?》。