我所知道的關於 script 標籤的一切

發表於2016-06-28

正如你可能知道的,script 標籤是用來指定在網頁上執行哪個 JavaScript 的。Script 標籤可以直接包含 JavaScript 程式碼,或者指向一個 JavaScript 外鏈 URL。

Script 標籤按照它們出現的順序被執行

下面的程式碼很直觀地說明了這一點:

使用外鏈資源時載入次序沒有那麼直觀,但依然是成立的:

如果你混合使用外鏈和內聯的 JavaScript,這個規則同樣適用。

這意味著如果你的網站有很慢的指令碼在頁面較前部分被載入,你的網頁載入就會被顯著拖慢。這也意味著後載入的指令碼可以依賴先載入的指令碼。

頁面元素在它之前的所有指令碼都載入完畢之前是不會執行渲染的。這意味著你可以你可以在頁面載入之前在網頁上做各種瘋狂的事情,當然前提是你不在意因此而造成的效能問題。

然而這個規則不適用於你在網頁載入完成之後通過 document.appendChild 之類的方法新增 script 標籤到 DOM 中。這些標籤會根據瀏覽器請求處理完成的先後執行指令碼,不再保證載入順序。

當一個 script 標籤被執行,在它之前的 HTML 元素可以訪問(但是在它之後的還不能用)

你可以想象 HTML 解析器一個標籤一個標籤地訪問文件,當它解析到 script 標籤時,馬上執行其中的 JavaScript。這意味著只有當開始標籤出現在當前指令碼之前的 DOM 節點才可以在當前 JavaScript 中被訪問(通過 querySelectorALl,jQuery 等等)。

一個有用的推論是 document.head 在任何寫在網頁上的 JavaScript 幾乎總是可用。document.body 只有當你將 script 標籤寫在 <body> 標籤中或者它之後的時候才可用。

asyncdefer

HTML5 新增了兩個工具來控制指令碼的執行。

  • async 表示“不用馬上執行它”。更具體地它表示:我不介意你在整個網頁載入完成之後執行這個指令碼,把它放在其他指令碼執行之後。這對於統計分析指令碼來說非常有用,因為頁面上沒有其他的程式碼需要依賴於統計指令碼執行。定義一個頁面需要的變數或函式在 async 的程式碼中是不行的,因為你沒有方法知道什麼時候 async 程式碼將會被實際執行。
  • defer 表示“等待頁面解析完成之後執行”。它大致等價於將你的指令碼繫結到DOMContentedLoaded 事件,或者使用 jQuery.ready。當這個程式碼被執行,DOM 中的一切元素都可用。不同於 async,所有加了 defer 的指令碼將會按照它們出現在 HTML 頁面中的順序執行,它只是推遲到 HTML 頁面解析完畢後開始執行。

type 屬性

從歷史上看(自 Netsacpe 2 誕生起),在 script 標籤上是否寫上 type=text/javascript 沒有什麼關係。如果你通過 type 設定一個非 JavaScript 的 MIME 型別,瀏覽器不會執行它。當你想要定義你自己的語言時,這會很酷:

這段程式碼實際執行結果由你自己決定,例如:

定義 runEmeraldCode 函式留給你們作為練習。

如果你有特別的需要,你也可以重寫頁面上 script 標籤的預設 type,方法是通過一個 meta 標籤:

或者一個請求返回一個 Content-Script-Type header。

可以讀一下 Web 上奇怪的指令碼語言的一個簡短歷史這篇文章有關於 type 用法的更詳細資訊。

integrity 屬性?

integrity 屬性是子資源完整性新規範的一部分。它允許你為指令碼檔案將包含的內容內容提供一個 hash。這意味著可以防止在傳輸的時候內容丟失或者被惡意修改。就算使用了 SSL,這個規範也是有意義的,因為有時候你要載入的資源是你無法控制的站外資源,比如 code.jquery.com

如果你選擇使用它,你要在 script 標籤裡包含一個 hash 型別以及 hash 值,將它們以連字元隔開。看起來類似下面這樣:

我還沒有看到有人用了它,然而如果你知道有哪個網站用了,可以在下面評論。

還可以用 crossorigin

雖然還沒有完全被標準化,但是一些瀏覽器支援 crossorigin 屬性。基本的想法是,瀏覽器會限制對非同源資源的使用(同源資源是指相同的協議、hostname 以及埠,例如: `http://google.com:80)。

這是為了防止你,例如,向你的競爭對手網站發請求,登出你的使用者在對方網站的賬號(這不好!)。這個問題牽扯到 script 標籤雖然有點意外,但如果實現了 crossorigin,你只要加一個 handler 到window.onerror 事件上,就能在看控制檯上看到一些警告資訊,提示你引入了一個不該引入的外站指令碼。在安全的瀏覽器下,除非你指定 crossorigin 屬性,不然載入外站指令碼不會出錯。

crossorgin 不是一個神奇的安全手段,它所做的只是讓瀏覽器啟用正常的 CORS 訪問檢查,發起一個 OPTIONS 請求並檢查 Access-Control header。

document.currentScript

IE 不支援的一個新奇的東西是個叫做 document.currentScript 的屬性。它指向當前正在被執行的指令碼。如果你想要從你嵌入的 script 標籤中拿一些屬性來用,它會非常有用。我個人非常高興它還沒有被完全支援,否則它會讓我們在一部分工作中渴望嵌入更復雜的程式碼。

onafterscriptexecute?!

這個超沒用,因為它只被 Firefox 支援。使用 onbeforescriptexecute 能讓你繫結事件到每一個指令碼的執行前和執行後,這很酷。

如果你對這個感到好奇,你可以研究下。event 物件包含一個被執行的指令碼的引用,而 before 事件能通過 perventDefault() 取消指令碼的執行。

for / event

到今天, HTML5 規範包含了一個很少見的,以前是 IE 特殊的方法來綁一段程式碼到一個事件。你應該能向下面這樣讓一段程式碼不被執行直到頁面載入完成:

這段程式碼在 Chrome 或者 Firefox 下不能實際工作,但是它依然能夠在 IE 下工作。

NOSCRIPT

如同你父母一樣,很難相信 JavaScript 也曾經年少過。曾經有過這樣一段時間你不能確定是否一個瀏覽器支援 JavaScript。更糟的是,你甚至不能確定那個瀏覽器能識別 script 標籤。而如果一個瀏覽器不能識別標籤,它應該會將它渲染成一個 inline 元素,意味著你所有 JavaScript 程式碼會被作為文字渲染在頁面上!

幸運地是,規範已經能足夠有效地避免這個情況發生,你只需要將你的程式碼包在 HTML 註釋裡,那些不支援指令碼的瀏覽器會把下面的文字當做註釋:

當然,像很多事情一樣,XHTML將這變得更糟。XML用特殊的方法來轉義可能包含結束標籤的內容,這是 CDATA 的來歷:

像上面這樣寫,你的程式碼可以是一個規範的 XHTML。它對實際功能沒有什麼影響,但是它對你作為一個 Web 開發者的榮譽也許很重要(現在這個時代,誰還用 XHTML 啊——譯者注)。

瀏覽器也包含一個有用的方法來讓你把那些不支援 JavaScript 人趕走,通過 noscript 標籤。<noscript> 標籤裡的內容只有瀏覽器不支援指令碼的時候才會被渲染出來:

如果你有敏銳的觀察力,你會意識到 noscript 不接受 type 引數,這使得那些使用別的type 型別的指令碼的頁面上如果出現 noscript 會顯得有點歧義。noscript 實際行為在各個瀏覽器下有所不同。

Script 標籤和 innerHTML

通過 DOM 動態新增到頁面上的 script 標籤會被瀏覽器執行:

通過 innerHTML 動態新增到頁面上的 script 標籤則不會被執行:

為什麼會是這樣的原因不是很確定,但是它解決了一個小問題:“是否有一個辦法讓一個 script 標籤在 Chrome 程式碼檢查器中顯示但不實際執行?”你可以利用這個來對你的同事做惡作劇。

相關文章