浏览器允许我们跟踪外部资源的加载情况,包括脚本、iframe、图片等。
它有两个事件
onload
– 加载成功,onerror
– 发生错误。
加载脚本
假设我们需要加载第三方脚本并调用其中驻留的函数。
我们可以动态加载它,如下所示
let script = document.createElement('script');
script.src = "my.js";
document.head.append(script);
…但如何运行在该脚本内声明的函数?我们需要等到脚本加载后,才能调用它。
对于我们自己的脚本,我们可以在此处使用JavaScript 模块,但第三方库并未广泛采用它们。
script.onload
主要帮助程序是load
事件。它在脚本加载并执行后触发。
例如
let script = document.createElement('script');
// can load any script, from any domain
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
document.head.append(script);
script.onload = function() {
// the script creates a variable "_"
alert( _.VERSION ); // shows library version
};
因此,在onload
中,我们可以使用脚本变量、运行函数等。
…如果加载失败怎么办?例如,没有这样的脚本(错误 404)或服务器已关闭(不可用)。
script.onerror
脚本加载期间发生的错误可以在error
事件中进行跟踪。
例如,让我们请求一个不存在的脚本
let script = document.createElement('script');
script.src = "https://example.com/404.js"; // no such script
document.head.append(script);
script.onerror = function() {
alert("Error loading " + this.src); // Error loading https://example.com/404.js
};
请注意,我们无法在此处获取 HTTP 错误详细信息。我们不知道它是错误 404、500 还是其他错误。只是加载失败了。
事件onload
/onerror
仅跟踪加载本身。
脚本处理和执行期间可能发生的错误不在这些事件的范围内。也就是说:如果脚本成功加载,则onload
触发,即使其中存在编程错误。要跟踪脚本错误,可以使用window.onerror
全局处理程序。
其他资源
load
和error
事件也适用于其他资源,基本上适用于任何具有外部src
的资源。
例如
let img = document.createElement('img');
img.src = "https://js.cx/clipart/train.gif"; // (*)
img.onload = function() {
alert(`Image loaded, size ${img.width}x${img.height}`);
};
img.onerror = function() {
alert("Error occurred while loading image");
};
不过有一些说明
- 大多数资源在添加到文档时开始加载。但
<img>
是一个例外。它在获得 src(*)
时开始加载。 - 对于
<iframe>
,iframe.onload
事件在 iframe 加载完成时触发,无论加载成功还是出错。
这是出于历史原因。
跨域策略
有一条规则:一个网站的脚本无法访问另一个网站的内容。因此,例如,https://facebook.com
上的脚本无法读取 https://gmail.com
上用户的邮箱。
或者更确切地说,一个来源(域/端口/协议三元组)无法访问另一个来源的内容。因此,即使我们有子域或只是另一个端口,这些也是不同的来源,彼此无法访问。
此规则也影响来自其他域的资源。
如果我们正在使用来自另一个域的脚本,并且其中存在错误,我们将无法获取错误详细信息。
例如,我们采用一个由单个(错误的)函数调用组成的脚本 error.js
// 📁 error.js
noSuchFunction();
现在从它所在的同一站点加载它
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="/article/onload-onerror/crossorigin/error.js"></script>
我们可以看到一个良好的错误报告,如下所示
Uncaught ReferenceError: noSuchFunction is not defined
https://javascript.js.cn/article/onload-onerror/crossorigin/error.js, 1:1
现在让我们从另一个域加载相同的脚本
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
报告不同,如下所示
Script error.
, 0:0
详细信息可能因浏览器而异,但思路是一样的:任何有关脚本内部的信息(包括错误堆栈跟踪)都会被隐藏。正是因为它来自另一个域。
为什么我们需要错误详细信息?
有很多服务(我们可以构建自己的服务)使用 window.onerror
侦听全局错误,保存错误并提供一个界面来访问和分析它们。这很好,因为我们可以看到由我们的用户触发的真实错误。但如果脚本来自另一个源,那么其中就没有太多关于错误的信息,正如我们刚才看到的。
类似的跨域策略 (CORS) 也适用于其他类型的资源。
为了允许跨域访问,<script>
标签需要具有 crossorigin
属性,此外远程服务器必须提供特殊标头。
跨域访问有三个级别
- 没有
crossorigin
属性 – 禁止访问。 crossorigin="anonymous"
– 如果服务器使用标头Access-Control-Allow-Origin
响应*
或我们的源,则允许访问。浏览器不会向远程服务器发送授权信息和 cookie。crossorigin="use-credentials"
– 如果服务器使用标头Access-Control-Allow-Origin
响应我们的源和Access-Control-Allow-Credentials: true
,则允许访问。浏览器向远程服务器发送授权信息和 cookie。
您可以在章节 获取:跨域请求 中阅读有关跨域访问的更多信息。它描述了用于网络请求的 fetch
方法,但策略完全相同。
诸如“cookie”之类的东西超出了我们当前的范围,但您可以在章节 Cookie,document.cookie 中阅读有关它们的信息。
在我们的案例中,我们没有任何 crossorigin 属性。因此禁止跨域访问。让我们添加它。
我们可以在 "anonymous"
(不发送 cookie,需要一个服务器端标头)和 "use-credentials"
(也发送 cookie,需要两个服务器端标头)之间进行选择。
如果我们不关心 cookie,那么 "anonymous"
是可行的方法
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script crossorigin="anonymous" src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
现在,假设服务器提供 Access-Control-Allow-Origin
标头,那么一切都很好。我们有完整的错误报告。
摘要
图像 <img>
、外部样式、脚本和其他资源提供 load
和 error
事件来跟踪它们的加载
load
在加载成功时触发,error
在加载失败时触发。
唯一的例外是 <iframe>
:出于历史原因,它始终触发 load
,对于任何加载完成,即使找不到页面也是如此。
readystatechange
事件也适用于资源,但很少使用,因为 load/error
事件更简单。
评论
<code>
标签,对于多行代码,请用<pre>
标签将它们包装起来,对于超过 10 行的代码,请使用沙箱(plnkr,jsbin,codepen…)