瀏覽器允許我們追蹤外部資源的載入,例如腳本、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://javascriptinfo.dev.org.tw/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:跨來源要求 中進一步了解跨來源存取。它描述了用於網路要求的 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…)