窺探 Script 標籤(步入現代 Web 開發的魔法世界)

窺探 Script 標籤

0x01 什麼是 script 標籤?

script 標籤容許你包含一些動態腳本或數據塊到文檔中,script 標籤是非閉合的,你也能夠將動態腳本或數據塊當作 script 的文本節點。就是內聯腳本。javascript

通常咱們最經常使用的就是寫一些 JavaScript 腳本在 script 標籤裏,可是 script 也能夠用來存儲一些數據,好比當你設置 type="text/react" 的 script 時就能夠在裏面放 react 代碼,可是遊覽器是不會執行它沒法識別的 type 的,所以 script 還能夠用來存放一些臨時 APP 數據。html

<script src="game-engine.js"></script>
<script type="text/x-game-map">
........U.........e
o............A....e
.....A.....AAA....e
.A..AAA...AAAAA...e
</script>

也能夠經過 documents.scripts[0].text 獲取到第一個腳本的內容,能夠修改它,可是不會有任何做用。前端

0x02 src 屬性

當你指定了 src 屬性時,外部腳本的內容是不受腳本內容限制的;同時你的 script 標籤內必須是空的。若是沒有指定 src,就稱這段腳本是內聯的,內聯腳本受到腳本內容限制。java

什麼是腳本內容限制?react

<script>
    alert('hello <script') // 報錯, DOM 解析器會認爲 <script 是一個 script 標籤開頭
    alert('hello <!--')    // 報錯,DOM 解析器會認爲 <!-- 是一個註釋開頭
    
    if (1<script) { }      // 報錯,DOM 解析器會認爲 <script 是一個 script 標籤開頭
      if (x<!--y ) { }       // 報錯,DOM 節氣息會認爲 <!-- 是一個註釋開頭
  
      alert('hello <\script') // 正常,添加了轉義
    alert('hello <\!--')    // 正常,添加了轉義
</script>

因此看得出來,若是你使用打包工具,爲了減小 CRP 而將腳本內聯到文檔裏,代碼要注意是否符合腳本內容限制;若是你還壓縮了代碼,更須要注意這一點。es6

0x03 defer 和 async 屬性

<script defer=defer src="xxx"></script> <!-- 這段腳本不會阻塞 DOM 解析,會併發的下載腳本,並在 DOM 解析完成以後纔會執行 -->
<script async src="xxx"></script> <!-- 這段腳本不會阻塞 DOM 解析,會併發的下載腳本,並在腳本下載完成後暫停 DOM 解析,而後執行腳本 -->

0x04 type=module和 nomodule 屬性

在 script 中,默認的 type="text/javascript",還能夠是 JavaScript MIME 中的任意一種。若是 script 裏寫的是 JavaScript,推薦省略 type 屬性。不指定 defer 和 async 下的經典腳本的執行會阻塞 DOM 解析。web

若是 type=module,則說明標籤引用的是一個 ES 模塊。segmentfault

<script type="module">
  import {addTextToBody} from './utils.js';

  addTextToBody('Modules are pretty cool.');
</script>

// utils.js
export function addTextToBody(text) {
  const div = document.createElement('div');
  div.textContent = text;
  document.body.appendChild(div);
}

僅僅支持「裸導入」跨域

// Supported:
import {foo} from 'https://jakearchibald.com/utils/bar.js';
import {foo} from '/utils/bar.js';
import {foo} from './bar.js';
import {foo} from '../bar.js';

// Not supported:
import {foo} from 'bar.js';
import {foo} from 'utils/bar.js';

支持 type=module 的遊覽器會自動忽略帶有 nomodule 的 script 標籤。方便你回退到不支持 module 的老式的用戶代理。服務器

<script type="module" src="module.js"></script>
<script nomodule src="fallback.js"></script>

並且 type=module 默認帶有 defer

<!-- This script will execute after… -->
<script type="module" src="1.js"></script>

<!-- …this script… -->
<script src="2.js"></script>

<!-- …but before this script. -->
<script defer src="3.js"></script>

執行的順序是 2.js,1.js,3.js

即使是內聯的 module,依然具備 defer 屬性。

<!-- This script will execute after… -->
<script type="module">
  addTextToBody("Inline module executed");
</script>

<!-- …this script… -->
<script src="1.js"></script>

<!-- …and this script… -->
<script defer>
  addTextToBody("Inline script executed");
</script>

<!-- …but before this script. -->
<script defer src="2.js"></script>

模塊腳本只會執行一次

<!-- 1.js only executes once -->
<script type="module" src="1.js"></script>
<script type="module" src="1.js"></script>
<script type="module">
  import "./1.js";
</script>

<!-- Whereas normal scripts execute multiple times -->
<script src="2.js"></script>
<script src="2.js"></script>

必須符合同源策略

<!-- This will not execute, as it fails a CORS check -->
<script type="module" src="https://….now.sh/no-cors"></script>

<!-- This will not execute, as one of its imports fails a CORS check -->
<script type="module">
  import 'https://….now.sh/no-cors';

  addTextToBody("This will not execute.");
</script>

<!-- This will execute as it passes CORS checks -->
<script type="module" src="https://….now.sh/cors"></script>

模塊腳本在跨域的時候默認是不帶 credentials 的。

<!-- Fetched with credentials (cookies etc) -->
<script src="1.js"></script>

<!-- Fetched without credentials -->
<script type="module" src="1.js"></script>

<!-- Fetched with credentials -->
<script type="module" crossorigin src="1.js?"></script>

<!-- Fetched without credentials -->
<script type="module" crossorigin src="https://other-origin/1.js"></script>

<!-- Fetched with credentials-->
<script type="module" crossorigin="use-credentials" src="https://other-origin/1.js?"></script>

下圖能夠很好的詮釋經典腳本和模塊腳本加載的不一樣

模塊腳本的依賴層級的增長會不會致使 CRP 長度的增長?

上圖能夠看出,層級很深的時候,用戶代理會花費大量的時間在等待依賴文件的傳輸和解析上,所以這會致使 CRP 長度的增長;不過 http2 push 的魔法使得用戶代理下載依賴文件的時間會大幅減小,服務器會分析模塊的依賴樹,而後在一次請求裏回傳全部依賴文件給用戶代理。具體的討論能夠看 Are ES6 modules in brwosers going to get loaded level-by-level 詳細討論了這個問題。

0x05 charset 屬性

給出腳本內容的編碼方式;沒有 src 的 script 不能設置該屬性,模塊腳本強行按 utf8 來解析。

0x06 noscript 標籤

noscript 標籤告訴遊覽器,若是你不支持腳本或腳本被禁用,那就顯示我裏面的內容。一般被用做腳本被禁用的回退方案。

0x07 最後

script 標籤真的使人感到興奮。

若是你以爲個人文章不錯,能夠關注個人

0x08 參考文章

相關文章
相關標籤/搜索