区分脚本运行错误和资源加载错误

510 阅读3分钟

原理分析

先引用一段MDN上的原文,说明什么是脚本运行错误和资源加载错误:

Error events are fired at various targets for different kinds of errors:

  • When a JavaScript runtime error (including syntax errors and exceptions thrown within handlers) occurs, an error event using interface ErrorEvent is fired at window and window.onerror() is invoked (as well as handlers attached by window.addEventListener (not only capturing)).
  • When a resource (such as an <img> or <script>) fails to load, an error event using interface Event is fired at the element that initiated the load, and the onerror() handler on the element is invoked. These error events do not bubble up to window, but (at least in Firefox) can be handled with a window.addEventListener configured with useCapture set to True.

从这段描述我们可以看出,脚本运行错误和资源加载错误都会抛出error事件,当我们使用window.addEventListener来监听错误时,该如何区分呢?

首先,从描述中可以找到一些思路:

  • 脚本运行错误事件是由window触发的,而资源加载错误事件是由DOM元素触发的;
  • 错误事件是不会冒泡的,所以只能在捕获阶段监听;

由此可见,如果是在同一个回调函数里面监听,判断e.target === window是个可行的方案。但是如果不在同一个回调函数里面呢,还有没有其它方法呢?

考虑到两者的触发对象不同,我们能不能分别使用windowdocument来监听呢?

接下来我们就要来看看捕获和冒泡了:

关于捕获和冒泡,可以用JavaScript高级程序设计书中的一张图来表示。

image

虽然如前面引用所描述的,error事件不会冒泡,但是我们可以通过设置addEventListener的第三个参数,在捕获阶段监听到error事件。

脚本运行错误最先被window捕获,然后就结束了;而资源加载错误的捕获阶段,却会先后经历:window=>document=>父级元素=>目标元素。所以,我们是可以分别使用windowdocument来监听脚本运行错误和资源加载错误的。

不过,不同于冒泡可以使用e.stopPropagation()来阻止冒泡,捕获阶段是一定会完整走完的。也就是说,如果在捕获阶段使用window.addEventListener监听error事件,资源加载错误也会被监听到。兜了一圈,又回来了。

所以,最后我们就要来看一个隐含的知识点了。

打开Chrome浏览器的Console,执行下面这段代码试试:

// 监听事件
const body = document.body;
window.addEventListener('customEvent', e => console.log('window'), false);
body.addEventListener('customEvent', e => console.log('body'), false);

// 创建并分发事件
const event = new CustomEvent('customEvent', { detail: { something: true }, bubbles: false });
body.dispatchEvent(event);

看到了什么?如我们所料,window没有被打印出来。但是body被打印出来了。

这里引用一段规范来说明为什么body能够被打印出来。

“DOM2级事件”规定事件流包括三个阶段,事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的事件捕获,为截获事件提供了机会。然后是实际的目标接收了事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。

大部分人都很熟悉捕获和冒泡这两个阶段,实现上还有一个中间阶段,叫做处于目标阶段。这意味着,触发事件的目标对象,不管事件是否支持冒泡,始终可以监听到该事件的触发!

联想到脚本运行错误是由window触发的,所以我们可以在冒泡阶段监听到由脚本运行错误触发的error事件!

代码实现

从上述的原理分析,我们可以很容易写出对应的实现代码:

// 监听脚本运行错误
window.addEventListener('error', runtimeErrorHandler, false);

// 监听资源加载错误
document.addEventListener('error', resourceErrorHandler, true);