前言
無論當前 JavaScript 程式碼是內嵌還是在外鏈檔案中,頁面的下載和渲染都必須停下來等待腳本執行完成。 JavaScript 執行過程耗時越久,瀏覽器等待回應使用者輸入的時間就越長。瀏覽器在下載和執行腳本時出現阻塞的原因在於,腳本可能會改變頁面或 JavaScript 的命名空間,它們會對後面頁面內容造成影響。
一個典型的例子就是在頁面中使用document.write() 。
JavaScript 程式碼內嵌範例
<html> <head> <title>Source Example</title> </head> <body> <p> <script type="text/javascript"> document.write("Today is " + (new Date()).toDateString()); </script> </p> </body> </html>
當瀏覽器遇到<script>標籤時,當前html 頁面無從獲知JavaScript 是否會向<p> 標籤添加內容,或引入其他元素,或甚至移除該標籤。因此,這時瀏覽器會停止處理頁面,先執行 JavaScript程式碼,然後再繼續解析和渲染頁面。同樣的情況也發生在使用 src 屬性載入 JavaScript的過程中,瀏覽器必須先花時間下載外鏈檔案中的程式碼,然後解析並執行它。在這個過程中,頁面渲染和使用者互動完全被阻塞了。 <br/></script>
腳本位置
HTML 4 規格指出 <script> 標籤可以放在 HTML 文件的<head>或<body>中,並允許出現多次。 web 開發人員一般習慣在 <head> 中載入外鏈的 JavaScript,接著用 <link> 標籤用來載入外鏈的 CSS 檔案或其他頁面資訊。 <br/></script>
低效率腳本位置範例
<html> <head> <title>Source Example</title> <script type="text/javascript" src="script1.js"></script> <script type="text/javascript" src="script2.js"></script> <script type="text/javascript" src="script3.js"></script> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <p>Hello world!</p> </body> </html>
然而這種常規的做法卻隱藏著嚴重的效能問題。在清單2 的範例中,當瀏覽器解析到<script> 標籤(第4 行)時,瀏覽器會停止解析其後的內容,而優先下載腳本文件,並執行其中的程式碼,這意味著,其後的styles.css 樣式檔案和<body>標籤都無法被載入,由於<body>標籤無法被載入,那麼頁面自然就無法渲染了。因此在該 JavaScript 程式碼完全執行完之前,頁面都是一片空白。 <br/></script>
由於腳本會阻塞頁面其他資源的下載,因此建議將所有<script>標籤盡可能放到<body>標籤的底部,以盡量減少對整個頁面下載的影響。 <br/></script>
推薦的程式碼放置位置範例
<html> <head> <title>Source Example</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <p>Hello world!</p> <!-- Example of efficient script positioning --> <script type="text/javascript" src="script1.js"></script> <script type="text/javascript" src="script2.js"></script> <script type="text/javascript" src="script3.js"></script> </body> </html>
這段程式碼展示了在 HTML 文件中放置<script>標籤的建議位置。儘管腳本下載會阻塞另一個腳本,但是頁面的大部分內容都已經下載完成並顯示給了用戶,因此頁面下載不會顯得太慢。這是優化 JavaScript 的首要規則:將腳本放在底部。 <br/></script>
組織腳本
由於每個<script>標籤初始下載時都會阻塞頁面渲染,所以減少頁麵包含的<script>標籤數量有助於改善這一情況。這不僅針對外鏈腳本,內嵌腳本的數量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個<script>標籤,都會因執行腳本而導致一定的延遲,因此最小化延遲時間將會明顯改善頁面的整體效能。 <br/></script>
這個問題在處理外鏈 JavaScript 檔案時略有不同。考慮到 HTTP 請求會帶來額外的效能開銷,因此下載單一 100Kb 的檔案將比下載 5 個 20Kb 的檔案更快。也就是說,減少頁面中外鏈腳本的數量將會改善效能。
通常一個大型網站或應用需要依賴數個 JavaScript 檔案。您可以把多個檔案合併成一個,這樣只需要引用一個<script>標籤,就可以減少效能消耗。文件合併的工作可透過離線的打包工具或一些即時的線上服務來實現。 <br/></script>
需要特別提醒的是,把一段內嵌腳本放在引用外鏈樣式表的之後會導致頁面阻塞去等待樣式表的下載。這樣做是為了確保內嵌腳本在執行時能獲得最精確的樣式資訊。因此,建議不要把內嵌腳本緊跟在標籤後面。
無阻塞的腳本
減少 JavaScript 檔案大小並限制 HTTP 請求數在功能豐富的 Web 應用或大型網站上並不總是可行。 Web 應用的功能越豐富,所需的 JavaScript 程式碼就越多,儘管下載單一較大的 JavaScript 檔案只產生一次 HTTP 請求,卻會鎖死瀏覽器的一大段時間。為避免這種情況,需要透過一些特定的技術向頁面中逐步載入 JavaScript 文件,這樣做在某種程度上來說不會阻塞瀏覽器。
無阻塞腳本的秘訣在於,在頁面載入完成後才載入 JavaScript 程式碼。這意味著在 window 物件的 onload事件觸發後再下載腳本。有多種方式可以實現這一效果。
延遲載入腳本
HTML 4 为3f1c4e4b6b16bbbd69b2ee476dc4f83a标签定义了一个扩展属性:defer。Defer 属性指明本元素所含的脚本不会修改 DOM,因此代码能安全地延迟执行。defer 属性只被 IE 4 和 Firefox 3.5 更高版本的浏览器所支持,所以它不是一个理想的跨浏览器解决方案。在其他浏览器中,defer 属性会被直接忽略,因此3f1c4e4b6b16bbbd69b2ee476dc4f83a标签会以默认的方式处理,也就是说会造成阻塞。然而,如果您的目标浏览器支持的话,这仍然是个有用的解决方案。
defer 属性使用方法示例
<script type="text/javascript" src="script1.js" defer></script>
带有 defer 属性的3f1c4e4b6b16bbbd69b2ee476dc4f83a标签可以放置在文档的任何位置。对应的 JavaScript 文件将在页面解析到3f1c4e4b6b16bbbd69b2ee476dc4f83a标签时开始下载,但不会执行,直到 DOM 加载完成,即onload事件触发前才会被执行。当一个带有 defer 属性的 JavaScript 文件下载时,它不会阻塞浏览器的其他进程,因此这类文件可以与其他资源文件一起并行下载。
任何带有 defer 属性的3f1c4e4b6b16bbbd69b2ee476dc4f83a元素在 DOM 完成加载之前都不会被执行,无论内嵌或者是外链脚本都是如此。清单 5 的例子展示了defer属性如何影响脚本行为:
defer 属性对脚本行为的影响
<html> <head> <title>Script Defer Example</title> </head> <body> <script type="text/javascript" defer> alert("defer"); </script> <script type="text/javascript"> alert("script"); </script> <script type="text/javascript"> window.onload = function(){ alert("load"); }; </script> </body> </html>
这段代码在页面处理过程中弹出三次对话框。不支持 defer 属性的浏览器的弹出顺序是:“defer”、“script”、“load”。而在支持 defer 属性的浏览器上,弹出的顺序则是:“script”、“defer”、“load”。请注意,带有 defer 属性的3f1c4e4b6b16bbbd69b2ee476dc4f83a元素不是跟在第二个后面执行,而是在 onload 事件被触发前被调用。
如果您的目标浏览器只包括 Internet Explorer 和 Firefox 3.5,那么 defer 脚本确实有用。如果您需要支持跨领域的多种浏览器,那么还有更一致的实现方式。
HTML 5 为3f1c4e4b6b16bbbd69b2ee476dc4f83a标签定义了一个新的扩展属性:async。它的作用和 defer 一样,能够异步地加载和执行脚本,不因为加载脚本而阻塞页面的加载。但是有一点需要注意,在有 async 的情况下,JavaScript 脚本一旦下载好了就会执行,所以很有可能不是按照原本的顺序来执行的。如果 JavaScript 脚本前后有依赖性,使用 async 就很有可能出现错误。
动态脚本元素
文档对象模型(DOM)允许您使用 JavaScript 动态创建 HTML 的几乎全部文档内容。3f1c4e4b6b16bbbd69b2ee476dc4f83a元素与页面其他元素一样,可以非常容易地通过标准 DOM 函数创建:
通过标准 DOM 函数创建3f1c4e4b6b16bbbd69b2ee476dc4f83a元素
var script = document.createElement ("script"); script.type = "text/javascript"; script.src = "script1.js"; document.getElementsByTagName("head")[0].appendChild(script);
新的3f1c4e4b6b16bbbd69b2ee476dc4f83a元素加载 script1.js 源文件。此文件当元素添加到页面之后立刻开始下载。此技术的重点在于:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。您甚至可以将这些代码放在93f0f5c25f18dab9d176bd4f6de5d30e部分而不会对其余部分的页面代码造成影响(除了用于下载文件的 HTTP 连接)。
当文件使用动态脚本节点下载时,返回的代码通常立即执行(除了 Firefox 和 Opera,他们将等待此前的所有动态脚本节点执行完毕)。当脚本是“自运行”类型时,这一机制运行正常,但是如果脚本只包含供页面其他脚本调用调用的接口,则会带来问题。这种情况下,您需要跟踪脚本下载完成并是否准备妥善。可以使用动态 3f1c4e4b6b16bbbd69b2ee476dc4f83a 节点发出事件得到相关信息。
Firefox、Opera, Chorme 和 Safari 3+会在3f1c4e4b6b16bbbd69b2ee476dc4f83a节点接收完成之后发出一个 onload 事件。您可以监听这一事件,以得到脚本准备好的通知:
通过监听 onload 事件加载 JavaScript 脚本
var script = document.createElement ("script") script.type = "text/javascript"; //Firefox, Opera, Chrome, Safari 3+ script.onload = function(){ alert("Script loaded!"); }; script.src = "script1.js"; document.getElementsByTagName("head")[0].appendChild(script);
Internet Explorer 支持另一种实现方式,它发出一个 readystatechange 事件。3f1c4e4b6b16bbbd69b2ee476dc4f83a元素有一个 readyState 属性,它的值随着下载外部文件的过程而改变。readyState 有五种取值:
微软文档上说,在3f1c4e4b6b16bbbd69b2ee476dc4f83a元素的生命周期中,readyState 的这些取值不一定全部出现,但并没有指出哪些取值总会被用到。实践中,我们最感兴趣的是“loaded”和“complete”状态。Internet Explorer 对这两个 readyState 值所表示的最终状态并不一致,有时3f1c4e4b6b16bbbd69b2ee476dc4f83a元素会得到“loader”却从不出现“complete”,但另外一些情况下出现“complete”而用不到“loaded”。最安全的办法就是在 readystatechange 事件中检查这两种状态,并且当其中一种状态出现时,删除 readystatechange 事件句柄(保证事件不会被处理两次):
通过检查 readyState 状态加载 JavaScript 脚本
var script = document.createElement("script") script.type = "text/javascript"; //Internet Explorer script.onreadystatechange = function(){ if (script.readyState == "loaded" || script.readyState == "complete"){ script.onreadystatechange = null; alert("Script loaded."); } }; script.src = "script1.js"; document.getElementsByTagName("head")[0].appendChild(script);
大多数情况下,您希望调用一个函数就可以实现 JavaScript 文件的动态加载。下面的函数封装了标准实现和 IE 实现所需的功能:
通过函数进行封装
function loadScript(url, callback){ var script = document.createElement ("script") script.type = "text/javascript"; if (script.readyState){ //IE script.onreadystatechange = function(){ if (script.readyState == "loaded" || script.readyState == "complete"){ script.onreadystatechange = null; callback(); } }; } else { //Others script.onload = function(){ callback(); }; } script.src = url; document.getElementsByTagName("head")[0].appendChild(script); }
此函数接收两个参数:JavaScript 文件的 URL,和一个当 JavaScript 接收完成时触发的回调函数。属性检查用于决定监视哪种事件。最后一步,设置 src 属性,并将3f1c4e4b6b16bbbd69b2ee476dc4f83a元素添加至页面。此 loadScript() 函数使用方法如下:
loadScript()函数使用方法
loadScript("script1.js", function(){ alert("File is loaded!"); });
您可以在页面中动态加载很多 JavaScript 文件,但要注意,浏览器不保证文件加载的顺序。所有主流浏览器之中,只有 Firefox 和 Opera 保证脚本按照您指定的顺序执行。其他浏览器将按照服务器返回它们的次序下载并运行不同的代码文件。您可以将下载操作串联在一起以保证他们的次序,如下:
通过 loadScript()函数加载多个 JavaScript 脚本
loadScript("script1.js", function(){ loadScript("script2.js", function(){ loadScript("script3.js", function(){ alert("All files are loaded!"); }); }); });
此代码等待 script1.js 可用之后才开始加载 script2.js,等 script2.js 可用之后才开始加载 script3.js。虽然此方法可行,但如果要下载和执行的文件很多,还是有些麻烦。如果多个文件的次序十分重要,更好的办法是将这些文件按照正确的次序连接成一个文件。独立文件可以一次性下载所有代码(由于这是异步进行的,使用一个大文件并没有什么损失)。
动态脚本加载是非阻塞 JavaScript 下载中最常用的模式,因为它可以跨浏览器,而且简单易用。
使用 XMLHttpRequest(XHR)对象
此技术首先创建一个 XHR 对象,然后下载 JavaScript 文件,接着用一个动态 3f1c4e4b6b16bbbd69b2ee476dc4f83a 元素将 JavaScript 代码注入页面。清单 12 是一个简单的例子:
通过 XHR 对象加载 JavaScript 脚本
var xhr = new XMLHttpRequest(); xhr.open("get", "script1.js", true); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ var script = document.createElement ("script"); script.type = "text/javascript"; script.text = xhr.responseText; document.body.appendChild(script); } } }; xhr.send(null);
此代码向服务器发送一个获取 script1.js 文件的 GET 请求。onreadystatechange 事件处理函数检查 readyState 是不是 4,然后检查 HTTP 状态码是不是有效(2XX 表示有效的回应,304 表示一个缓存响应)。如果收到了一个有效的响应,那么就创建一个新的3f1c4e4b6b16bbbd69b2ee476dc4f83a元素,将它的文本属性设置为从服务器接收到的 responseText 字符串。这样做实际上会创建一个带有内联代码的3f1c4e4b6b16bbbd69b2ee476dc4f83a元素。一旦新3f1c4e4b6b16bbbd69b2ee476dc4f83a元素被添加到文档,代码将被执行,并准备使用。
这种方法的主要优点是,您可以下载不立即执行的 JavaScript 代码。由于代码返回在3f1c4e4b6b16bbbd69b2ee476dc4f83a标签之外(换句话说不受3f1c4e4b6b16bbbd69b2ee476dc4f83a标签约束),它下载后不会自动执行,这使得您可以推迟执行,直到一切都准备好了。另一个优点是,同样的代码在所有现代浏览器中都不会引发异常。
此方法最主要的限制是:JavaScript 文件必须与页面放置在同一个域内,不能从 CDN 下载(CDN 指"内容投递网络(Content Delivery Network)",所以大型网页通常不采用 XHR 脚本注入技术。
总结
减少 JavaScript 对性能的影响有以下几种方法:
通过以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 网站和应用的实际性能。
补充js加载函数:
function loadJs(url, callback, charset) { var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); if ( !!charset) script.charset = "utf-8"; script.src = url; script.onload = script.onreadystatechange = function() { var f = script.readyState; if (f && f != "loaded" && f != "complete") return; script.onload = script.onreadystatechange = null; head.removeChild(script) if (callback) { callback() || callback }; }; head.appendChild(script); }
// js同步加载 function getScripts(i, linkArray, fn) { env || getEnv(); var script = document.createElement('script'); script.type = 'text/javascript'; script.src = linkArray[i]; var head = document.head || document.getElementsByTagName('head')[0]; head.appendChild(script); if (env.ie && 'onreadystatechange' in script && !('draggable' in script)){ //ie浏览器使用以下方式加载 script.onreadystatechange = function () { if (/loaded|complete/.test(script.readyState)) { script.onreadystatechange = null; if(i === linkArray.length-1) { if (fn) { fn(); } } else { getScripts(++i, linkArray, fn); } } }; }else{ script.onload = function() { if(i === linkArray.length-1) { if (fn) { fn(); } } else { getScripts(++i, linkArray, fn); } }; } }
// js存在依赖关系 依次加载 getScripts(0, [ 'http://caibaojian.com/demo/base.js', 'http://caibaojian.com/demo/reset.js'], function() { alert('callback'); });
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。
更多JavaScript提高加载和执行效率的方法相关文章请关注PHP中文网!