Javascript 資源加載:onload,onerror

2023-02-17 10:55 更新

瀏覽器允許我們跟蹤外部資源的加載 —— 腳本,iframe,圖片等。

這里有兩個(gè)事件:

  • ?onload? —— 成功加載,
  • ?onerror? —— 出現(xiàn) error。

加載腳本

假設(shè)我們需要加載第三方腳本,并調(diào)用其中的函數(shù)。

我們可以像這樣動(dòng)態(tài)加載它:

let script = document.createElement('script');
script.src = "my.js";

document.head.append(script);

……但如何運(yùn)行在該腳本中聲明的函數(shù)?我們需要等到該腳本加載完成,之后才能調(diào)用它。

請(qǐng)注意:

對(duì)于我們自己的腳本,可以使用 JavaScript module,但是它們并未被廣泛應(yīng)用于第三方庫(kù)。

script.onload

我們的得力助手是 ?load? 事件。它會(huì)在腳本加載并執(zhí)行完成時(shí)觸發(fā)。

例如:

let script = document.createElement('script');

// 可以從任意域(domain),加載任意腳本
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
document.head.append(script);

script.onload = function() {
  // 該腳本創(chuàng)建了一個(gè)變量 "_"
  alert( _.VERSION ); // 顯示庫(kù)的版本
};

因此,在 onload 中我們可以使用腳本中的變量,運(yùn)行函數(shù)等。

……如果加載失敗怎么辦?例如,這里沒(méi)有這樣的腳本(error 404)或者服務(wù)器宕機(jī)(不可用)。

script.onerror

發(fā)生在腳本加載期間的 error 會(huì)被 error 事件跟蹤到。

例如,我們請(qǐng)求一個(gè)不存在的腳本:

let script = document.createElement('script');
script.src = "https://example.com/404.js"; // 沒(méi)有這個(gè)腳本
document.head.append(script);

script.onerror = function() {
  alert("Error loading " + this.src); // Error loading https://example.com/404.js
};

請(qǐng)注意,在這里我們無(wú)法獲取更多 HTTP error 的詳細(xì)信息。我們不知道 error 是 404 還是 500 或者其他情況。只知道是加載失敗了。

重要:

onload/onerror 事件僅跟蹤加載本身。

在腳本處理和執(zhí)行期間可能發(fā)生的 error 超出了這些事件跟蹤的范圍。也就是說(shuō):如果腳本成功加載,則即使腳本中有編程 error,也會(huì)觸發(fā) onload 事件。如果要跟蹤腳本 error,可以使用 window.onerror 全局處理程序。

其他資源

load 和 error 事件也適用于其他資源,基本上(basically)適用于具有外部 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");
};

但是有一些注意事項(xiàng):

  • 大多數(shù)資源在被添加到文檔中后,便開(kāi)始加載。但是 ?<img>? 是個(gè)例外。它要等到獲得 src (?*?) 后才開(kāi)始加載。
  • 對(duì)于 ?<iframe>? 來(lái)說(shuō),iframe 加載完成時(shí)會(huì)觸發(fā) ?iframe.onload? 事件,無(wú)論是成功加載還是出現(xiàn) error。

這是出于歷史原因。

跨源策略

這里有一條規(guī)則:來(lái)自一個(gè)網(wǎng)站的腳本無(wú)法訪問(wèn)其他網(wǎng)站的內(nèi)容。例如,位于 https://facebook.com 的腳本無(wú)法讀取位于 https://gmail.com 的用戶郵箱。

或者,更確切地說(shuō),一個(gè)源(域/端口/協(xié)議三者)無(wú)法獲取另一個(gè)源(origin)的內(nèi)容。因此,即使我們有一個(gè)子域,或者僅僅是另一個(gè)端口,這都是不同的源,彼此無(wú)法相互訪問(wèn)。

這個(gè)規(guī)則還影響其他域的資源。

如果我們使用的是來(lái)自其他域的腳本,并且該腳本中存在 error,那么我們無(wú)法獲取 error 的詳細(xì)信息。

例如,讓我們使用一個(gè)腳本 error.js,該腳本只包含一個(gè)(錯(cuò)誤)函數(shù)調(diào)用:

//  error.js
noSuchFunction();

現(xiàn)在從它所在的同一個(gè)網(wǎng)站加載它:

<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>

我們可以看到一個(gè)很好的 error 報(bào)告,就像這樣:

Uncaught ReferenceError: noSuchFunction is not defined
https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1

現(xiàn)在,讓我們從另一個(gè)域中加載相同的腳本:

<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" rel="external nofollow"  rel="external nofollow" ></script>

此報(bào)告與上面那個(gè)示例中的不同,就像這樣:

Script error.
, 0:0

error 的詳細(xì)信息可能因?yàn)g覽器而異,但是原理是相同的:有關(guān)腳本內(nèi)部的任何信息(包括 error 堆棧跟蹤)都被隱藏了。正是因?yàn)樗鼇?lái)自于另一個(gè)域。

為什么我們需要 error 的詳細(xì)信息?

因?yàn)橛泻芏喾?wù)(我們也可以構(gòu)建自己的服務(wù))使用 window.onerror 監(jiān)聽(tīng)全局 error,保存 error 并提供訪問(wèn)和分析 error 的接口。這很好,因?yàn)槲覀兛梢钥吹接捎脩粲|發(fā)的實(shí)際中的 error。但是,如果一個(gè)腳本來(lái)自于另一個(gè)源(origin),那么正如我們剛剛看到的那樣,其中沒(méi)有太多有關(guān) error 的信息。

對(duì)其他類型的資源也執(zhí)行類似的跨源策略(CORS)。

要允許跨源訪問(wèn),<script> 標(biāo)簽需要具有 crossorigin 特性(attribute),并且遠(yuǎn)程服務(wù)器必須提供特殊的 header。

這里有三個(gè)級(jí)別的跨源訪問(wèn):

  1. 無(wú) ?crossorigin特性 —— 禁止訪問(wèn)。
  2. ?crossorigin="anonymous"? —— 如果服務(wù)器的響應(yīng)帶有包含 ?*? 或我們的源(origin)的 header ?Access-Control-Allow-Origin?,則允許訪問(wèn)。瀏覽器不會(huì)將授權(quán)信息和 cookie 發(fā)送到遠(yuǎn)程服務(wù)器。
  3. ?crossorigin="use-credentials"? —— 如果服務(wù)器發(fā)送回帶有我們的源的 header ?Access-Control-Allow-Origin? 和 ?Access-Control-Allow-Credentials: true?,則允許訪問(wèn)。瀏覽器會(huì)將授權(quán)信息和 cookie 發(fā)送到遠(yuǎn)程服務(wù)器。

請(qǐng)注意:

你可以在 Fetch:跨源請(qǐng)求 一章中了解有關(guān)跨源訪問(wèn)的更多信息。這一章描述了用于網(wǎng)絡(luò)請(qǐng)求的 fetch 方法,但策略是完全相同的。

諸如 “cookie” 之類的內(nèi)容超出了本章的范圍,但你可以在 Cookie,document.cookie 一章學(xué)習(xí)它們。

在我們的示例中沒(méi)有任何跨源特性(attribute)。因此,跨源訪問(wèn)被禁止。讓我們來(lái)添加它吧。

我們可以在 "anonymous"(不會(huì)發(fā)送 cookie,需要一個(gè)服務(wù)器端的 header)和 "use-credentials"(會(huì)發(fā)送 cookie,需要兩個(gè)服務(wù)器端的 header)之間進(jìn)行選擇。

如果我們不關(guān)心 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" rel="external nofollow"  rel="external nofollow" ></script>

現(xiàn)在,假設(shè)服務(wù)器提供了 Access-Control-Allow-Origin header,一切都正常。我們有了完整的 error 報(bào)告。

總結(jié)

圖片 <img>,外部樣式,腳本和其他資源都提供了 load 和 error 事件以跟蹤它們的加載:

  • ?load? 在成功加載時(shí)被觸發(fā)。
  • ?error? 在加載失敗時(shí)被觸發(fā)。

唯一的例外是 <iframe>:出于歷史原因,不管加載成功還是失敗,即使頁(yè)面沒(méi)有被找到,它都會(huì)觸發(fā) load 事件。

readystatechange 事件也適用于資源,但很少被使用,因?yàn)?nbsp;load/error 事件更簡(jiǎn)單。

任務(wù)


使用回調(diào)函數(shù)加載圖片

重要程度: 4

通常,圖片在被創(chuàng)建時(shí)才會(huì)被加載。所以,當(dāng)我們向頁(yè)面中添加 <img> 時(shí),用戶不會(huì)立即看到圖片。瀏覽器首先需要加載它。

為了立即顯示一張圖片,我們可以“提前”創(chuàng)建它,像這樣:

let img = document.createElement('img');
img.src = 'my.jpg';

瀏覽器開(kāi)始加載圖片,并將其保存到緩存中。以后,當(dāng)相同圖片出現(xiàn)在文檔中時(shí)(無(wú)論怎樣),它都會(huì)立即顯示。

創(chuàng)建一個(gè)函數(shù) preloadImages(sources, callback),來(lái)加載來(lái)自數(shù)組 source 的所有圖片,并在準(zhǔn)備就緒時(shí)運(yùn)行 callback。

例如,這段代碼將在圖片加載完成后顯示一個(gè) alert

function loaded() {
  alert("Images loaded")
}

preloadImages(["1.jpg", "2.jpg", "3.jpg"], loaded);

如果出現(xiàn)錯(cuò)誤,函數(shù)應(yīng)該仍假定圖片已經(jīng)“加載完成”。

換句話說(shuō),當(dāng)所有圖片都已加載完成,或出現(xiàn)錯(cuò)誤輸出時(shí),將執(zhí)行 callback。

例如,當(dāng)我們計(jì)劃顯示一個(gè)包含很多圖片的可滾動(dòng)圖冊(cè),并希望確保所有圖片都已加載完成時(shí),這個(gè)函數(shù)很有用。

在源文檔中,你可以找到指向測(cè)試圖片的鏈接,以及檢查它們是否已加載完成的代碼。它應(yīng)該輸出 300

打開(kāi)一個(gè)任務(wù)沙箱。


解決方案

算法:

  1. 為每個(gè)資源創(chuàng)建 ?img?。
  2. 為每個(gè)圖片添加 ?onload/onerror?。
  3. 在 ?onload? 或 ?onerror? 被觸發(fā)時(shí),增加計(jì)數(shù)器。
  4. 當(dāng)計(jì)數(shù)器值等于資源值時(shí) —— 我們完成了:?callback()?。

使用沙箱打開(kāi)解決方案。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)