11# 资源加载:onload 和 onerror
22
3- 浏览器允许跟踪外部资源的加载 —— 脚本、 iframes、 图像等。
3+ 浏览器允许跟踪外部资源的加载 —— 脚本, iframes, 图像等。
44
5- 有两个事件
5+ 它有两个事件:
66
77- ` onload ` —— 成功加载,
88- ` onerror ` —— 发生异常。
99
1010## 加载脚本
1111
12- 假设我们需要调用属于外部脚本的函数 。
12+ 假设我们需要调用属于第三方脚本的函数 。
1313
1414我们可以像这样动态加载:
1515
@@ -22,34 +22,38 @@ document.head.append(script);
2222
2323...但如何运行声明在脚本中的函数?我们需要等到脚本被加载后才能调用它。
2424
25+ ``` smart
26+ 对于我们自己的脚本,可以使用 [JavaScript modules](info:modules),但它们并没有被第三方库采用。
27+ ```
28+
2529### script.onload
2630
27- 主要得力于 ` load ` 事件。它在脚本被加载和执行后才被触发 。
31+ 主要得力于 ` load ` 事件。它在脚本被加载和执行后才会触发 。
2832
2933例如:
3034
3135``` js run untrusted
3236let script = document .createElement (' script' );
3337
34- // 可以从任意域名加载脚本
38+ // 可以从任意域名加载任意脚本
3539script .src = " https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
3640document .head .append (script);
3741
3842* ! *
3943script .onload = function () {
40- // 脚本创建了一个辅助函数 "_"
44+ // 脚本创建了一个辅助函数“_”
4145 alert (_); // 函数可用
4246};
4347*/ ! *
4448```
4549
46- 因此,在 ` onload ` 中我们使用脚本变量 、运行函数等。
50+ 因此,在 ` onload ` 中我们使用脚本中的变量 、运行函数等。
4751
4852...如果加载失败怎么办?比如,没有这样的脚本(错误 404)或者服务器宕机(不可用)。
4953
5054### script.onerror
5155
52- 发生在脚本(不是执行)期间的错误可以在 ` error ` 事件上进行追踪。
56+ 发生在脚本加载期间的错误可以在 ` error ` 事件上进行追踪。
5357
5458比如,我们请求一个不存在的脚本:
5559
@@ -65,19 +69,128 @@ script.onerror = function() {
6569*/ ! *
6670```
6771
68- 请注意,我们无法再这获取错误的更多细节。我们不知道错误是 404 还是 500 或者其他情况,只知道是加载失败了。
72+ 请注意,我们无法在这里获取更多 HTTP 错误细节。我们不知道错误是 404 还是 500 或者其他情况,只知道是加载失败了。
73+
74+ ``` warn
75+ `onload`/`onerror` 事件仅仅跟踪加载本身。
76+ 跟踪脚本处理和执行期间的错误超出了这些事件的范围。如果要追踪脚本错误,可以使用 `window.onerror` 全局处理器。
77+ ```
6978
7079## 其他资源
7180
72- ` load ` 和 ` error ` 事件也适用于其他资源。但是也存在细微的差别 。
81+ ` load ` 和 ` error ` 事件也适用于几乎任何具有外部 ` src ` 的资源 。
7382
7483例如:
7584
76- ` <img> ` ,` <link> ` (外部样式表)
77- : ` load ` 和 ` error ` 事件都如期运行。
85+ ``` js run
86+ let img = document .createElement (' img' );
87+ img .src = " https://js.cx/clipart/train.gif" ; // (*)
88+ img .onload = function () {
89+ alert (` Image loaded, size ${ img .width } x${ img .height } ` );
90+ };
91+ img .onerror = function () {
92+ alert (" Error occurred while loading image" );
93+ };
94+ ```
95+
96+ 但也有一些注意事项:
97+
98+ - 对于大部分资源来说,当他们被添加到文档时就开始加载。但是 ` <img> ` 是个例外。它要等到获取 src ` (*) ` 属性后才开始加载。
99+ - 对于 ` <iframe> ` 来说,只有当 iframe 加载完成,不论时成功还是失败,` iframe.onload ` 事件才会触发,
100+
101+ 这是出于历史遗留原因。
102+
103+ ## 跨域策略
104+
105+ 这里有个规则:来自一个站点的脚本无法访问其他站点的内容。即 ` https://facebook.com ` 中的脚本不能获取 ` https://gmail.com ` 中的用户邮箱。
106+
107+ 或者,更确切地说,一个源(domain/port/protocol 三者)不能获取另外一个源中的内容。因此,即使我们有一个子域名,或者仅仅是另外一个端口,这都是不同的源,彼此不能互相访问。
108+
109+ 这个规则同样适用于其他域中的资源。
110+
111+ 如果我们需要使用来自其他域名的脚本,并且脚本里面存在错误,那么我们就不能获取错误信息。
112+
113+ 例如,我们调用脚本中一个(错误)函数:
114+
115+ ``` js
116+ // 📁 error.js
117+ noSuchFunction ();
118+ ```
119+
120+ 现在从我们的域名中加载它:
121+
122+ ``` html run height=0
123+ <script >
124+ window .onerror = function (message , url , line , col , errorObj ) {
125+ alert (` ${ message} \n ${ url} , ${ line} :${ col} ` );
126+ };
127+ </script >
128+ <script src =" /article/onload-onerror/crossorigin/error.js" ></script >
129+ ```
130+
131+ 我们可以看到一个很好的错误报告,就像这样:
132+
133+ ```
134+ Uncaught ReferenceError: noSuchFunction is not defined
135+ https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1
136+ ```
137+
138+ 现在,再从其他域名中加载这个脚本:
139+
140+ ``` html run height=0
141+ <script >
142+ window .onerror = function (message , url , line , col , errorObj ) {
143+ alert (` ${ message} \n ${ url} , ${ line} :${ col} ` );
144+ };
145+ </script >
146+ <script src =" https://cors.javascript.info/article/onload-onerror/crossorigin/error.js" ></script >
147+ ```
148+
149+ 错误报告与上面不同,就像这样:
150+
151+ ```
152+ Script error.
153+ , 0:0
154+ ```
155+
156+ 错误细节可能因浏览器而异,但是原理是相同的:任何有关脚本内部的信息都是不可见的。确切来说是因为它来自于其他域。
157+
158+ 我们为什么需要细节信息?
159+
160+ 因为有很多服务(我们也可以自己建立)来监听 ` window.onerror ` ,在服务器上保存错误信息,并分析它们,以提供用户相应的错误页面。这很棒,因为我们可以看到由用户触发的真实错误。但是我们不能获得来自其他域名的脚本的任何错误信息。
161+
162+ 类似的跨源策略(CORS)也适用于其他类型资源。
163+
164+ ** 要允许跨域访问,我们需要 ` crossorigin ` 属性,同样对于服务器也需要提供特殊的响应头。**
165+
166+ 这里有三个级别的跨源访问:
167+
168+ 1 . ** 无 ` crossorigin ` 属性*** —— 禁止访问。
169+ 2 . ** ` crossorigin="anonymous" ` ** —— 如果服务器的响应头中提供了 ` Access-Control-Allow-Origin ` 为 ` * ` 或者为我们的源,那么就可以访问。浏览器不会将授权信息和 cookies 发送到远程服务器。
170+ 3 . ** ` crossorigin="use-credentials" ` ** —— 如果服务器的响应头提供了 ` Access-Control-Allow-Origin ` 为我们的源,且提供了 ` Access-Control-Allow-Credentials: true ` ,那么我们就可以访问。浏览器此时会将授权信息和 cookies 发送到远程服务器。
171+
172+ ``` smart
173+ 你可以在 <info:fetch-crossorigin> 中阅读更多关于跨源访问的信息。这里虽然它是以 `fetch` 方法作为网络请求的,但策略都是相同的。
174+
175+ 诸如“cookies”这类的内容超出了本章的范围,你可以在 <info:cookie> 章节获取到关于它的更多信息。
176+ ```
177+
178+ 在我们的的例子中没有任何 crossorigin 属性。因此禁止跨域访问。让我们来加上它吧。
179+
180+ 我们可以选择“anonymous”(不会发送 cookies,但是需要服务端响应头)和“use-credentials”(发送 cookes,需要两个服务端响应头)中的任意一个。
181+
182+ 如果我们不关心“cookies”,那么可以使用` “anonymous” ` :
183+
184+ ``` html run height=0
185+ <script >
186+ window .onerror = function (message , url , line , col , errorObj ) {
187+ alert (` ${ message} \n ${ url} , ${ line} :${ col} ` );
188+ };
189+ </script >
190+ <script *!*crossorigin =" anonymous" * /!* src =" https://cors.javascript.info/article/onload-onerror/crossorigin/error.js" ></script >
191+ ```
78192
79- ` <iframe> `
80- : 只有当 iframe 加载完成时会发生 ` load ` 事件。在成功或失败的情况下,都会触发它。这是历史原因。
193+ 现在,假设服务器提供 ` Access-Control-Allow-Origin ` 头,一切都正常。我们有完整的错误报告。
81194
82195## 总结
83196
@@ -86,6 +199,6 @@ script.onerror = function() {
86199- ` load ` 在成功加载时被触发。
87200- ` error ` 在加载失败时被触发。
88201
89- 只有 ` <iframe> ` 特殊:出于历史原因,即使页面没有被找到,它总会触发 ` load ` 来完成任何加载 。
202+ 只有 ` <iframe> ` 特殊:出于历史原因,即使页面没有被找到,它总会触发 ` load ` 来完成任何加载过程 。
90203
91204` readystatechange ` 事件也适用于资源,但很少被使用,因为 ` load/error ` 事件更简单。
0 commit comments