Skip to content

Commit 8c94529

Browse files
authored
Update 2-ui/5-loading/03-onload-onerror/article.md
1 parent 931b412 commit 8c94529

1 file changed

Lines changed: 128 additions & 15 deletions

File tree

Lines changed: 128 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
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
3236
let script = document.createElement('script');
3337

34-
// 可以从任意域名加载脚本
38+
// 可以从任意域名加载任意脚本
3539
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
3640
document.head.append(script);
3741

3842
*!*
3943
script.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

Comments
 (0)