習(xí)目標(biāo):了解JavaScript是如何與HTML結(jié)合來創(chuàng)建動態(tài)網(wǎng)頁,網(wǎng)頁中嵌入JavaScript的不同方式,JavaScript的內(nèi)容類型及其與<script>的關(guān)系
<script>是由Netscape創(chuàng)造出來,后來加到HTML規(guī)范中的。
<script>有8個屬性:
1、async:表示立即開始下載腳本,但不能阻止其他頁面動作,比如下載資源或者等待其他腳本加載。只對外部腳本文件有效。
2、charset:使用src屬性指定代碼字符集。這個屬性很少用,因為大多數(shù)瀏覽器不在乎它的值。
3、crossorigin;配置資源請求的CORS(跨源資源共享)設(shè)置。默認(rèn)情況下不使用CORS。crossorigin = “anonymous”配置文件請求不用設(shè)置憑據(jù)標(biāo)志。crossorigin = ”use-credentials“設(shè)置憑據(jù)標(biāo)志,意味著出站請求會包含憑據(jù)。
4、defer:表示腳本可以延遲到文檔全部解析和顯示后再執(zhí)行。新版本中只能用于外部腳本。
5、integrity:允許比對接收到的資源和指定的加密簽名以驗證子資源完整性(SRI,Subresource integrity),如果驗證簽名不匹配則腳本不會執(zhí)行。這個屬性可以用于確保內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN,Content Delivery Network)不會提供惡意內(nèi)容。
6、language:此屬性已被廢止。
7、src:表示包含外部要執(zhí)行的代碼的外部文件。
8、type:代替language,表示代碼塊中腳本語言的內(nèi)容類型(也稱為MIME類型),按照慣例這個值始終都是”text/JavaScript“,盡管”text/JavaScript“和”text/ecmascript“都已經(jīng)廢棄。JavaScript文件的MIME類型通常是”application/x-javascript“,不過給type屬性這個值的話可能會導(dǎo)致腳本被忽略。在非IE的瀏覽器中有效的值還有”application/JavaScript“和”application/ecmascript"。如果這個值是module,則代碼會被當(dāng)成是ES6模塊,而且只有這時候代碼中才能出現(xiàn)import和export關(guān)鍵字。
使用<script>的方式有內(nèi)聯(lián)和外嵌兩種,只要把code寫入<script>code</script>中就好,code中要是包含字符串“<script>”,只要加上轉(zhuǎn)義字符“\”即可。
如果要外嵌JavaScript代碼只要使用src屬性來鏈接外部文件即可如:
<script src=“example.js”></script>
XHTML 文檔中,可以忽略結(jié)束標(biāo)簽寫成<script src=“example.js”/>即可,但是這在HTML中不能使用。
過去把JavaScript和CSS一起寫在head中,但是這意味著必須下載所有code并解析和解釋完成后才開始渲染頁面,對于JavaScript很多的頁面會導(dǎo)致頁面渲染速度過慢,為解決這個問題,JavaScript一般寫在body元素的頁面內(nèi)容的最后邊,如下
<html>
<head></head>
<body>
message
<script>code<\script>
<\body>
</html>
在外聯(lián)JavaScript時可以使用defer屬性來推遲腳本的運行??梢詫懗桑?/p>
<html>
<head>
<script defer src = "example.js">code<\script>
</head>
<body>
message
<\body>
</html>
async屬性從腳本處理方式上與defer類似,但是不同的是標(biāo)記async的腳本并不能保證腳本按照他們的出現(xiàn)順序執(zhí)行,比如:
<html>
<head>
<script sync src = "example1.js">code<\script>
<script sync src = "example2.js">code<\script>
</head>
<body>
message
<\body>
</html>
不能保證example1比example2先執(zhí)行。
除了<script>以外還可以用其他方式加載腳本。因為JavaScript可以使用DOM API,所以通過向DOM中動態(tài)地加入script元素同樣可以加載指定腳本。只要創(chuàng)建一個script元素并將其添加到DOM即可。
let script = document.createElement('script');
script.src = 'gibberish.js';
document.head.appendChild(script);
當(dāng)然,在把 HTMLElement 元素添加到 DOM 且執(zhí)行到這段代碼之前不會發(fā)送請求。默認(rèn)情況下,以這種方式創(chuàng)建的<script>元素是以異步方式加載的,相當(dāng)于添加了 async 屬性。不過這樣做可能會有問題,因為所有瀏覽器都支持 createElement()方法,但不是所有瀏覽器都支持 async 屬性。因此,如果要統(tǒng)一動態(tài)腳本的加載行為,可以明確將其設(shè)置為同步加載:
let script = document.createElement('script');
script.src = 'gibberish.js';
script.async = false;
document.head.appendChild(script);
以這種方式獲取的資源對瀏覽器預(yù)加載器是不可見的。這會嚴(yán)重影響它們在資源獲取隊列中的優(yōu)先級。根據(jù)應(yīng)用程序的工作方式以及怎么使用,這種方式可能會嚴(yán)重影響性能。要想讓預(yù)加載器知道這些動態(tài)請求文件的存在,可以在文檔頭部顯式聲明它們:
<link rel="preload" href="gibberish.js">
可擴展超文本標(biāo)記語言(XHTML,Extensible HyperText Markup Language)是將 HTML 作為 XML的應(yīng)用重新包裝的結(jié)果。與 HTML 不同,在 XHTML 中使用 JavaScript 必須指定 type 屬性且值為text/javascript,HTML 中則可以沒有這個屬性。XHTML 雖然已經(jīng)退出歷史舞臺,但實踐中偶爾可能也會遇到遺留代碼,為此本節(jié)稍作介紹。在 XHTML 中編寫代碼的規(guī)則比 HTML 中嚴(yán)格,這會影響使用<script>元素嵌入 JavaScript 代碼。下面的代碼塊雖然在 HTML 中有效,但在 XHML 中是無效的。
<script type="text/javascript">
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
</script>
在 HTML 中,解析<script>元素會應(yīng)用特殊規(guī)則。XHTML 中則沒有這些規(guī)則。這意味著 a < b語句中的小于號(<)會被解釋成一個標(biāo)簽的開始,并且由于作為標(biāo)簽開始的小于號后面不能有空格,這會導(dǎo)致語法錯誤。避免 XHTML 中這種語法錯誤的方法有兩種。第一種是把所有小于號(<)都替換成對應(yīng)的 HTML實體形式(<)。結(jié)果代碼就是這樣的:
<script type="text/javascript">
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
</script>
這樣代碼就可以在 XHTML 頁面中運行了。不過,缺點是會影響閱讀。好在還有另一種方法。第二種方法是把所有代碼都包含到一個 CDATA 塊中。在 XHTML(及 XML)中,CDATA 塊表示文檔中可以包含任意文本的區(qū)塊,其內(nèi)容不作為標(biāo)簽來解析,因此可以在其中包含任意字符,包括小于號,并且不會引發(fā)語法錯誤。使用 CDATA 的格式如下:
<script type="text/javascript"><![CDATA[
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
]]></script>
在兼容 XHTML 的瀏覽器中,這樣能解決問題。但在不支持 CDATA 塊的非 XHTML 兼容瀏覽器中則不行。為此,CDATA 標(biāo)記必須使用 JavaScript 注釋來抵消:
<script type="text/javascript">
//<![CDATA[
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
//]]>
</script>
這種格式適用于所有現(xiàn)代瀏覽器。雖然有點黑科技的味道,但它可以通過 XHTML 驗證,而且對XHTML 之前的瀏覽器也能優(yōu)雅地降級。
自 1995 年 Netscape 2 發(fā)布以來,所有瀏覽器都將 JavaScript 作為默認(rèn)的編程語言。type 屬性使用一個 MIME 類型字符串來標(biāo)識<script>的內(nèi)容,但 MIME 類型并沒有跨瀏覽器標(biāo)準(zhǔn)化。即使瀏覽器默認(rèn)使用 JavaScript,在某些情況下某個無效或無法識別的 MIME 類型也可能導(dǎo)致瀏覽器跳過(不執(zhí)行)相關(guān)代碼。因此,除非你使用 XHTML 或<script>標(biāo)簽要求或包含非 JavaScript 代碼,最佳做法是不指定 type 屬性。在最初采用 script 元素時,它標(biāo)志著開始走向與傳統(tǒng) HTML 解析不同的流程。對這個元素需要應(yīng)用特殊的解析規(guī)則,而這在不支持 JavaScript 的瀏覽器(特別是 Mosaic)中會導(dǎo)致問題。不支持的瀏覽器會把<script>元素的內(nèi)容輸出到頁面上,從而破壞頁面的外觀。Netscape 聯(lián)合 Mosaic 拿出了一個解決方案,對不支持 JavaScript 的瀏覽器隱藏嵌入的 JavaScript 代碼。最終方案是把腳本代碼包含在一個 HTML 注釋中,像這樣:
<script><!--
function sayHi(){
console.log("Hi!");
}
//--></script>
使用這種格式,Mosaic 等瀏覽器就可以忽略<script>標(biāo)簽中的內(nèi)容,而支持 JavaScript 的瀏覽器則必須識別這種模式,將其中的內(nèi)容作為 JavaScript 來解析。雖然這種格式仍然可以被所有瀏覽器識別和解析,但已經(jīng)不再必要,而且不應(yīng)該再使用了。在XHTML 模式下,這種格式也會導(dǎo)致腳本被忽略,因為代碼處于有效的 XML 注釋當(dāng)中。
雖然可以直接在 HTML 文件中嵌入 JavaScript 代碼,但通常認(rèn)為最佳實踐是盡可能將 JavaScript 代碼放在外部文件中。不過這個最佳實踐并不是明確的強制性規(guī)則。推薦使用外部文件的理由如下。
? 可維護性。JavaScript 代碼如果分散到很多 HTML 頁面,會導(dǎo)致維護困難。而用一個目錄保存所有 JavaScript 文件,則更容易維護,這樣開發(fā)者就可以獨立于使用它們的 HTML 頁面來編輯代碼。
? 緩存。瀏覽器會根據(jù)特定的設(shè)置緩存所有外部鏈接的 JavaScript 文件,這意味著如果兩個頁面都用到同一個文件,則該文件只需下載一次。這最終意味著頁面加載更快。
? 適應(yīng)未來。通過把 JavaScript 放到外部文件中,就不必考慮用 XHTML 或前面提到的注釋黑科技。包含外部 JavaScript 文件的語法在 HTML 和 XHTML 中是一樣的。在配置瀏覽器請求外部文件時,要重點考慮的一點是它們會占用多少帶寬。在 SPDY/HTTP2 中,預(yù)請求的消耗已顯著降低,以輕量、獨立 JavaScript 組件形式向客戶端送達腳本更具優(yōu)勢。比如,第一個頁面包含如下腳本:
<script src="mainA.js"></script>
<script src="component1.js"></script>
<script src="component2.js"></script>
<script src="component3.js"></script>
...
后續(xù)頁面可能包含如下腳本:
<script src="mainB.js"></script>
<script src="component3.js"></script>
<script src="component4.js"></script>
<script src="component5.js"></script>
...
在初次請求時,如果瀏覽器支持 SPDY/HTTP2,就可以從同一個地方取得一批文件,并將它們逐個放到瀏覽器緩存中。從瀏覽器角度看,通過 SPDY/HTTP2 獲取所有這些獨立的資源與獲取一個大JavaScript 文件的延遲差不多。在第二個頁面請求時,由于你已經(jīng)把應(yīng)用程序切割成了輕量可緩存的文件,第二個頁面也依賴的某些組件此時已經(jīng)存在于瀏覽器緩存中了。當(dāng)然,這里假設(shè)瀏覽器支持 SPDY/HTTP2,只有比較新的瀏覽器才滿足。如果你還想支持那些比較老的瀏覽器,可能還是用一個大文件更合適。
IE5.5 發(fā)明了文檔模式的概念,即可以使用 doctype 切換文檔模式。最初的文檔模式有兩種:混雜模式(quirks mode)和標(biāo)準(zhǔn)模式(standards mode)。前者讓 IE 像 IE5 一樣(支持一些非標(biāo)準(zhǔn)的特性),后者讓 IE 具有兼容標(biāo)準(zhǔn)的行為。雖然這兩種模式的主要區(qū)別只體現(xiàn)在通過 CSS 渲染的內(nèi)容方面,但對JavaScript 也有一些關(guān)聯(lián)影響,或稱為副作用。本書會經(jīng)常提到這些副作用。
IE 初次支持文檔模式切換以后,其他瀏覽器也跟著實現(xiàn)了。隨著瀏覽器的普遍實現(xiàn),又出現(xiàn)了第三種文檔模式:準(zhǔn)標(biāo)準(zhǔn)模式(almost standards mode)。這種模式下的瀏覽器支持很多標(biāo)準(zhǔn)的特性,但是沒有標(biāo)準(zhǔn)規(guī)定得那么嚴(yán)格。主要區(qū)別在于如何對待圖片元素周圍的空白(在表格中使用圖片時最明顯)。
混雜模式在所有瀏覽器中都以省略文檔開頭的 doctype 聲明作為開關(guān)。這種約定并不合理,因為混雜模式在不同瀏覽器中的差異非常大,不使用黑科技基本上就沒有瀏覽器一致性可言。標(biāo)準(zhǔn)模式通過下列幾種文檔類型聲明開啟:
<!-- HTML 4.01 Strict -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<!-- XHTML 1.0 Strict -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- HTML5 -->
<!DOCTYPE html>
準(zhǔn)標(biāo)準(zhǔn)模式通過過渡性文檔類型(Transitional)和框架集文檔類型(Frameset)來觸發(fā):
<!-- HTML 4.01 Transitional -->
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<!-- HTML 4.01 Frameset -->
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Frameset//EN"
"http://www.w3.org/TR/html4/frameset.dtd">
<!-- XHTML 1.0 Transitional -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- XHTML 1.0 Frameset -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Frameset//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
準(zhǔn)標(biāo)準(zhǔn)模式與標(biāo)準(zhǔn)模式非常接近,很少需要區(qū)分。人們在說到“標(biāo)準(zhǔn)模式”時,可能指其中任何一個。而對文檔模式的檢測(本書后面會討論)也不會區(qū)分它們。本書后面所說的標(biāo)準(zhǔn)模式,指的就是除混雜模式以外的模式。
針對早期瀏覽器不支持 JavaScript 的問題,需要一個頁面優(yōu)雅降級的處理方案。最終,<noscript>元素出現(xiàn),被用于給不支持 JavaScript 的瀏覽器提供替代內(nèi)容。雖然如今的瀏覽器已經(jīng) 100%支持JavaScript,但對于禁用 JavaScript 的瀏覽器來說,這個元素仍然有它的用處。<noscript>元素可以包含任何可以出現(xiàn)在<body>中的 HTML 元素,<script>除外。在下列兩種情況下,瀏覽器將顯示包含在<noscript>中的內(nèi)容:
? 瀏覽器不支持腳本;
? 瀏覽器對腳本的支持被關(guān)閉。任何一個條件被滿足,包含在<noscript>中的內(nèi)容就會被渲染。否則,瀏覽器不會渲染<noscript>中的內(nèi)容。
下面是一個例子:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script defer="defer" src="example1.js"></script>
<script defer="defer" src="example2.js"></script>
</head>
<body>
<noscript>
<p>This page requires a JavaScript-enabled browser.</p>
</noscript>
</body>
</html>
這個例子是在腳本不可用時讓瀏覽器顯示一段話。如果瀏覽器支持腳本,則用戶永遠不會看到它。
JavaScript 是通過<script>元素插入到 HTML 頁面中的。這個元素可用于把 JavaScript 代碼嵌入到HTML 頁面中,跟其他標(biāo)記混合在一起,也可用于引入保存在外部文件中的 JavaScript。本章的重點可以總結(jié)如下。
? 要包含外部 JavaScript 文件,必須將 src 屬性設(shè)置為要包含文件的 URL。文件可以跟網(wǎng)頁在同一臺服務(wù)器上,也可以位于完全不同的域。
? 所有<script>元素會依照它們在網(wǎng)頁中出現(xiàn)的次序被解釋。在不使用 defer 和 async 屬性的情況下,包含在<script>元素中的代碼必須嚴(yán)格按次序解釋。
? 對不推遲執(zhí)行的腳本,瀏覽器必須解釋完位于<script>元素中的代碼,然后才能繼續(xù)渲染頁面的剩余部分。為此,通常應(yīng)該把<script>元素放到頁面末尾,介于主內(nèi)容之后及</body>標(biāo)簽之前。
? 可以使用 defer 屬性把腳本推遲到文檔渲染完畢后再執(zhí)行。推遲的腳本原則上按照它們被列出的次序執(zhí)行。
? 可以使用 async 屬性表示腳本不需要等待其他腳本,同時也不阻塞文檔渲染,即異步加載。異步腳本不能保證按照它們在頁面中出現(xiàn)的次序執(zhí)行。
? 通過使用<noscript>元素,可以指定在瀏覽器不支持腳本時顯示的內(nèi)容。如果瀏覽器支持并啟用腳本,則<noscript>元素中的任何內(nèi)容都不會被渲染。
來了~歡迎轉(zhuǎn)發(fā)、收藏,記得點個贊唄。^_^
無論當(dāng)前 JavaScript 代碼是內(nèi)嵌還是在外鏈文件中,頁面的下載和渲染都必須停下來等待腳本執(zhí)行完成。JavaScript 執(zhí)行過程耗時越久,瀏覽器等待響應(yīng)用戶輸入的時間就越長。瀏覽器在下載和執(zhí)行腳本時出現(xiàn)阻塞的原因在于,腳本可能會改變頁面或 JavaScript 的命名空間,它們對后面頁面內(nèi)容造成影響。一個典型的例子就是在頁面中使用document.write()。例如清單 1。
清單 1 JavaScript 代碼內(nèi)嵌示例
當(dāng)瀏覽器遇到<script>標(biāo)簽時,當(dāng)前 HTML 頁面無從獲知 JavaScript 是否會向<p> 標(biāo)簽添加內(nèi)容,或引入其他元素,或甚至移除該標(biāo)簽。因此,這時瀏覽器會停止處理頁面,先執(zhí)行 JavaScript代碼,然后再繼續(xù)解析和渲染頁面。同樣的情況也發(fā)生在使用 src 屬性加載 JavaScript的過程中,瀏覽器必須先花時間下載外鏈文件中的代碼,然后解析并執(zhí)行它。在這個過程中,頁面渲染和用戶交互完全被阻塞了。
HTML 4 規(guī)范指出 <script> 標(biāo)簽可以放在 HTML 文檔的<head>或<body>中,并允許出現(xiàn)多次。Web 開發(fā)人員一般習(xí)慣在 <head> 中加載外鏈的 JavaScript,接著用 <link> 標(biāo)簽用來加載外鏈的 CSS 文件或者其他頁面信息。例如清單 2。
清單 2 低效率腳本位置示例
然而這種常規(guī)的做法卻隱藏著嚴(yán)重的性能問題。在清單 2 的示例中,當(dāng)瀏覽器解析到 <script> 標(biāo)簽(第 4 行)時,瀏覽器會停止解析其后的內(nèi)容,而優(yōu)先下載腳本文件,并執(zhí)行其中的代碼,這意味著,其后的 styles.css 樣式文件和<body>標(biāo)簽都無法被加載,由于<body>標(biāo)簽無法被加載,那么頁面自然就無法渲染了。因此在該 JavaScript 代碼完全執(zhí)行完之前,頁面都是一片空白。圖 1 描述了頁面加載過程中腳本和樣式文件的下載過程。
圖 1 JavaScript 文件的加載和執(zhí)行阻塞其他文件的下載
我們可以發(fā)現(xiàn)一個有趣的現(xiàn)象:第一個 JavaScript 文件開始下載,與此同時阻塞了頁面其他文件的下載。此外,從 script1.js 下載完成到 script2.js 開始下載前存在一個延時,這段時間正好是 script1.js 文件的執(zhí)行過程。每個文件必須等到前一個文件下載并執(zhí)行完成才會開始下載。在這些文件逐個下載過程中,用戶看到的是一片空白的頁面。
從 IE 8、Firefox 3.5、Safari 4 和 Chrome 2 開始都允許并行下載 JavaScript 文件。這是個好消息,因為<script>標(biāo)簽在下載外部資源時不會阻塞其他<script>標(biāo)簽。遺憾的是,JavaScript 下載過程仍然會阻塞其他資源的下載,比如樣式文件和圖片。盡管腳本的下載過程不會互相影響,但頁面仍然必須等待所有 JavaScript 代碼下載并執(zhí)行完成才能繼續(xù)。因此,盡管最新的瀏覽器通過允許并行下載提高了性能,但問題尚未完全解決,腳本阻塞仍然是一個問題。
由于腳本會阻塞頁面其他資源的下載,因此推薦將所有<script>標(biāo)簽盡可能放到<body>標(biāo)簽的底部,以盡量減少對整個頁面下載的影響。例如清單 3
清單 3 推薦的代碼放置位置示例
這段代碼展示了在 HTML 文檔中放置<script>標(biāo)簽的推薦位置。盡管腳本下載會阻塞另一個腳本,但是頁面的大部分內(nèi)容都已經(jīng)下載完成并顯示給了用戶,因此頁面下載不會顯得太慢。
這是優(yōu)化 JavaScript 的首要規(guī)則:將腳本放在底部。
由于每個<script>標(biāo)簽初始下載時都會阻塞頁面渲染,所以減少頁面包含的<script>標(biāo)簽數(shù)量有助于改善這一情況。這不僅針對外鏈腳本,內(nèi)嵌腳本的數(shù)量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個<script>標(biāo)簽,都會因執(zhí)行腳本而導(dǎo)致一定的延時,因此最小化延遲時間將會明顯改善頁面的總體性能。
這個問題在處理外鏈 JavaScript 文件時略有不同??紤]到 HTTP 請求會帶來額外的性能開銷,因此下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減少頁面中外鏈腳本的數(shù)量將會改善性能。
通常一個大型網(wǎng)站或應(yīng)用需要依賴數(shù)個 JavaScript 文件。您可以把多個文件合并成一個,這樣只需要引用一個<script>標(biāo)簽,就可以減少性能消耗。文件合并的工作可通過離線的打包工具或者一些實時的在線服務(wù)來實現(xiàn)。
需要特別提醒的是,把一段內(nèi)嵌腳本放在引用外鏈樣式表的<link>之后會導(dǎo)致頁面阻塞去等待樣式表的下載。這樣做是為了確保內(nèi)嵌腳本在執(zhí)行時能獲得最精確的樣式信息。因此,建議不要把內(nèi)嵌腳本緊跟在<link>標(biāo)簽后面。
減少 JavaScript 文件大小并限制 HTTP 請求數(shù)在功能豐富的 Web 應(yīng)用或大型網(wǎng)站上并不總是可行。Web 應(yīng)用的功能越豐富,所需要的 JavaScript 代碼就越多,盡管下載單個較大的 JavaScript 文件只產(chǎn)生一次 HTTP 請求,卻會鎖死瀏覽器的一大段時間。為避免這種情況,需要通過一些特定的技術(shù)向頁面中逐步加載 JavaScript 文件,這樣做在某種程度上來說不會阻塞瀏覽器。
無阻塞腳本的秘訣在于,在頁面加載完成后才加載 JavaScript 代碼。這就意味著在 window 對象的 onload事件觸發(fā)后再下載腳本。有多種方式可以實現(xiàn)這一效果。
HTML 4 為<script>標(biāo)簽定義了一個擴展屬性:defer。Defer 屬性指明本元素所含的腳本不會修改 DOM,因此代碼能安全地延遲執(zhí)行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,所以它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器中,defer 屬性會被直接忽略,因此<script>標(biāo)簽會以默認(rèn)的方式處理,也就是說會造成阻塞。然而,如果您的目標(biāo)瀏覽器支持的話,這仍然是個有用的解決方案。清單 4 是一個例子
清單 4 defer 屬性使用方法示例
<script type="text/javascript" src="script1.js" defer></script>
帶有 defer 屬性的<script>標(biāo)簽可以放置在文檔的任何位置。對應(yīng)的 JavaScript 文件將在頁面解析到<script>標(biāo)簽時開始下載,但不會執(zhí)行,直到 DOM 加載完成,即onload事件觸發(fā)前才會被執(zhí)行。當(dāng)一個帶有 defer 屬性的 JavaScript 文件下載時,它不會阻塞瀏覽器的其他進程,因此這類文件可以與其他資源文件一起并行下載。
任何帶有 defer 屬性的<script>元素在 DOM 完成加載之前都不會被執(zhí)行,無論內(nèi)嵌或者是外鏈腳本都是如此。清單 5 的例子展示了defer屬性如何影響腳本行為:
清單 5 defer 屬性對腳本行為的影響
這段代碼在頁面處理過程中彈出三次對話框。不支持 defer 屬性的瀏覽器的彈出順序是:"defer"、"script"、"load"。而在支持 defer 屬性的瀏覽器上,彈出的順序則是:"script"、"defer"、"load"。請注意,帶有 defer 屬性的<script>元素不是跟在第二個后面執(zhí)行,而是在 onload 事件被觸發(fā)前被調(diào)用。
如果您的目標(biāo)瀏覽器只包括 Internet Explorer 和 Firefox 3.5,那么 defer 腳本確實有用。如果您需要支持跨領(lǐng)域的多種瀏覽器,那么還有更一致的實現(xiàn)方式。
HTML 5 為<script>標(biāo)簽定義了一個新的擴展屬性:async。它的作用和 defer 一樣,能夠異步地加載和執(zhí)行腳本,不因為加載腳本而阻塞頁面的加載。但是有一點需要注意,在有 async 的情況下,JavaScript 腳本一旦下載好了就會執(zhí)行,所以很有可能不是按照原本的順序來執(zhí)行的。如果 JavaScript 腳本前后有依賴性,使用 async 就很有可能出現(xiàn)錯誤。
文檔對象模型(DOM)允許您使用 JavaScript 動態(tài)創(chuàng)建 HTML 的幾乎全部文檔內(nèi)容。<script>元素與頁面其他元素一樣,可以非常容易地通過標(biāo)準(zhǔn) DOM 函數(shù)創(chuàng)建:
清單 6 通過標(biāo)準(zhǔn) DOM 函數(shù)創(chuàng)建<script>元素
新的<script>元素加載 script1.js 源文件。此文件當(dāng)元素添加到頁面之后立刻開始下載。此技術(shù)的重點在于:無論在何處啟動下載,文件的下載和運行都不會阻塞其他頁面處理過程。您甚至可以將這些代碼放在<head>部分而不會對其余部分的頁面代碼造成影響(除了用于下載文件的 HTTP 連接)。
當(dāng)文件使用動態(tài)腳本節(jié)點下載時,返回的代碼通常立即執(zhí)行(除了 Firefox 和 Opera,他們將等待此前的所有動態(tài)腳本節(jié)點執(zhí)行完畢)。當(dāng)腳本是"自運行"類型時,這一機制運行正常,但是如果腳本只包含供頁面其他腳本調(diào)用調(diào)用的接口,則會帶來問題。這種情況下,您需要跟蹤腳本下載完成并是否準(zhǔn)備妥善??梢允褂脛討B(tài) <script> 節(jié)點發(fā)出事件得到相關(guān)信息。
Firefox、Opera, Chorme 和 Safari 3+會在<script>節(jié)點接收完成之后發(fā)出一個 onload 事件。您可以監(jiān)聽這一事件,以得到腳本準(zhǔn)備好的通知:
清單 7 通過監(jiān)聽 onload 事件加載 JavaScript 腳本
Internet Explorer 支持另一種實現(xiàn)方式,它發(fā)出一個 readystatechange 事件。<script>元素有一個 readyState 屬性,它的值隨著下載外部文件的過程而改變。readyState 有五種取值:
· "uninitialized":默認(rèn)狀態(tài)
· "loading":下載開始
· "loaded":下載完成
· "interactive":下載完成但尚不可用
· "complete":所有數(shù)據(jù)已經(jīng)準(zhǔn)備好
微軟文檔上說,在<script>元素的生命周期中,readyState 的這些取值不一定全部出現(xiàn),但并沒有指出哪些取值總會被用到。實踐中,我們最感興趣的是"loaded"和"complete"狀態(tài)。Internet Explorer 對這兩個 readyState 值所表示的最終狀態(tài)并不一致,有時<script>元素會得到"loader"卻從不出現(xiàn)"complete",但另外一些情況下出現(xiàn)"complete"而用不到"loaded"。最安全的辦法就是在 readystatechange 事件中檢查這兩種狀態(tài),并且當(dāng)其中一種狀態(tài)出現(xiàn)時,刪除 readystatechange 事件句柄(保證事件不會被處理兩次):
清單 8 通過檢查 readyState 狀態(tài)加載 JavaScript 腳本
大多數(shù)情況下,您希望調(diào)用一個函數(shù)就可以實現(xiàn) JavaScript 文件的動態(tài)加載。下面的函數(shù)封裝了標(biāo)準(zhǔn)實現(xiàn)和 IE 實現(xiàn)所需的功能:
清單 9 通過函數(shù)進行封裝
此函數(shù)接收兩個參數(shù):JavaScript 文件的 URL,和一個當(dāng) JavaScript 接收完成時觸發(fā)的回調(diào)函數(shù)。屬性檢查用于決定監(jiān)視哪種事件。最后一步,設(shè)置 src 屬性,并將<script>元素添加至頁面。此 loadScript() 函數(shù)使用方法如下:
清單 10 loadScript()函數(shù)使用方法
您可以在頁面中動態(tài)加載很多 JavaScript 文件,但要注意,瀏覽器不保證文件加載的順序。所有主流瀏覽器之中,只有 Firefox 和 Opera 保證腳本按照您指定的順序執(zhí)行。其他瀏覽器將按照服務(wù)器返回它們的次序下載并運行不同的代碼文件。您可以將下載操作串聯(lián)在一起以保證他們的次序,如下:
清單 11 通過 loadScript()函數(shù)加載多個 JavaScript 腳本
此代碼等待 script1.js 可用之后才開始加載 script2.js,等 script2.js 可用之后才開始加載 script3.js。雖然此方法可行,但如果要下載和執(zhí)行的文件很多,還是有些麻煩。如果多個文件的次序十分重要,更好的辦法是將這些文件按照正確的次序連接成一個文件。獨立文件可以一次性下載所有代碼(由于這是異步進行的,使用一個大文件并沒有什么損失)。
動態(tài)腳本加載是非阻塞 JavaScript 下載中最常用的模式,因為它可以跨瀏覽器,而且簡單易用。
此技術(shù)首先創(chuàng)建一個 XHR 對象,然后下載 JavaScript 文件,接著用一個動態(tài) <script> 元素將 JavaScript 代碼注入頁面。清單 12 是一個簡單的例子:
清單 12 通過 XHR 對象加載 JavaScript 腳本
此代碼向服務(wù)器發(fā)送一個獲取 script1.js 文件的 GET 請求。onreadystatechange 事件處理函數(shù)檢查 readyState 是不是 4,然后檢查 HTTP 狀態(tài)碼是不是有效(2XX 表示有效的回應(yīng),304 表示一個緩存響應(yīng))。如果收到了一個有效的響應(yīng),那么就創(chuàng)建一個新的<script>元素,將它的文本屬性設(shè)置為從服務(wù)器接收到的 responseText 字符串。這樣做實際上會創(chuàng)建一個帶有內(nèi)聯(lián)代碼的<script>元素。一旦新<script>元素被添加到文檔,代碼將被執(zhí)行,并準(zhǔn)備使用。
這種方法的主要優(yōu)點是,您可以下載不立即執(zhí)行的 JavaScript 代碼。由于代碼返回在<script>標(biāo)簽之外(換句話說不受<script>標(biāo)簽約束),它下載后不會自動執(zhí)行,這使得您可以推遲執(zhí)行,直到一切都準(zhǔn)備好了。另一個優(yōu)點是,同樣的代碼在所有現(xiàn)代瀏覽器中都不會引發(fā)異常。
此方法最主要的限制是:JavaScript 文件必須與頁面放置在同一個域內(nèi),不能從 CDN 下載(CDN 指"內(nèi)容投遞網(wǎng)絡(luò)(Content Delivery Network)",所以大型網(wǎng)頁通常不采用 XHR 腳本注入技術(shù)。
一般而言,減少 JavaScript 對性能的影響有以下幾種方法:
· A.將所有的<script>標(biāo)簽放到頁面底部,也就是</body>閉合標(biāo)簽之前,這能確保在腳本執(zhí)行前頁面已經(jīng)完成了渲染。
· B.盡可能地合并腳本。頁面中的<script>標(biāo)簽越少,加載也就越快,響應(yīng)也越迅速。無論是外鏈腳本還是內(nèi)嵌腳本都是如此。
· C.采用無阻塞下載 JavaScript 腳本的方法:
· a-使用<script>標(biāo)簽的 defer 屬性(僅適用于 IE 和 Firefox 3.5 以上版本);
· b-使用動態(tài)創(chuàng)建的<script>元素來下載并執(zhí)行代碼;
· c-使用 XHR 對象下載 JavaScript 代碼并注入頁面中。
通過以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 網(wǎng)站和應(yīng)用的實際性能。
看完了,別忘了收藏、轉(zhuǎn)發(fā)、點個贊,更別忘了要關(guān)注本號!^_^
JavaScript輸出文本
用JavaScript改變HTML元素
一個外部JavaScript
實例解析
JavaScript 語句、注釋和代碼塊
JavaScript 語句
JavaScript 代碼塊
JavaScript 單行注釋
JavaScript 多行注釋
使用單行注釋來防止執(zhí)行
使用多行注釋來防止執(zhí)行
實例解析
JavaScript 變量
聲明一個變量,為它賦值,然后顯示出來
實例解析
JavaScript 條件語句 If ... Else
If 語句
If...else 語句
隨機鏈接
Switch 語句
實例解析
JavaScript 消息框
Alert(警告)框
帶有換行的警告框
確認(rèn)框
提示框
實例解析
JavaScript 函數(shù)
函數(shù)
帶有參數(shù)的函數(shù)
帶有參數(shù)的函數(shù) 2
返回值的函數(shù)
帶有參數(shù)并返回值的函數(shù)
實例解析
JavaScript 循環(huán)
For 循環(huán)
循環(huán)輸出 HTML 標(biāo)題
While 循環(huán)
Do while 循環(huán)
break 語句
continue 語句
使用 For...In 聲明來遍歷數(shù)組內(nèi)的元素
實例解析
JavaScript 事件
onclick事件
onmouseover 事件
實例解析
JavaScript 錯誤處理
try...catch 語句
帶有確認(rèn)框的 try...catch 語句
onerror 事件
實例解析
高級 JavaScript 實例
創(chuàng)建一個歡迎 cookie
簡單的計時
另一個簡單的計時
在一個無窮循環(huán)中的計時事件
帶有停止按鈕的無窮循環(huán)中的計時事件
使用計時事件制作的鐘表
創(chuàng)建對象的實例
創(chuàng)建用于對象的模板
如您還有不明白的可以在下面與我留言或是與我探討QQ群308855039,我們一起飛!
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。