JavaScript 提高加载和执行效率的方法

作者:寒星

时间:2026-10-21

浏览:476

本文深入解析 JavaScript 脚本对页面渲染的阻塞机制,探讨脚本放置位置、合并压缩及无阻塞加载技术。通过动态创建 script 节点、利用 defer 和 async 属性,以及 XHR 注入等手段,有效减少页面白屏时间,提升 Web 应用性能。

文章目录

前言

JavaScript 脚本阻塞机制

无论 JavaScript 代码是内嵌在 HTML 中还是通过外链文件引入,浏览器的下载和渲染进程都必须暂停,直到脚本执行完毕。脚本执行耗时越长,浏览器响应用户交互的延迟就越高。造成这一阻塞的根本原因在于,脚本可能修改页面 DOM 结构或 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> 标签时,由于无法预知 JavaScript 是否会向 <p> 标签添加内容、引入新元素或移除现有标签,浏览器会暂停页面处理,优先执行 JavaScript 代码,随后再继续解析和渲染页面。使用 src 属性加载外链脚本时同样如此,浏览器需先下载外链文件,再解析执行,此期间页面渲染和用户交互完全被阻塞。

脚本放置位置优化

脚本位置

HTML 4 规范允许 <script> 标签置于 <head> 或 <body> 中,且可出现多次。Web 开发人员通常习惯在 <head> 中加载外链 JavaScript,并配合 <link> 标签加载 CSS 或其他页面资源。

低效率脚本位置示例


<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 示例所示,当浏览器解析到第 4 行的 <script> 标签时,会停止解析后续内容,优先下载并执行脚本。这意味着 styles.css 样式文件和 <body> 标签均无法加载,导致页面无法渲染,在 JavaScript 代码执行完毕前,页面呈现空白状态。

由于脚本会阻塞其他资源下载,建议将所有 <script> 标签尽可能放置在 <body> 标签底部,以最小化对页面整体下载的影响。

推荐的代码放置位置示例


<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>

该示例展示了 <script> 标签在 HTML 文档中的推荐位置。尽管脚本下载会阻塞其他脚本,但页面主体内容已下载并展示给用户,因此页面加载速度不会显得过慢。这是优化 JavaScript 的首要原则:将脚本置于页面底部。

脚本组织与合并

组织脚本

由于每个 <script> 标签在初始下载时都会阻塞页面渲染,减少页面中 <script> 标签的数量有助于改善这一状况。这不仅适用于外链脚本,内嵌脚本的数量也需限制。浏览器每遇到一个 <script> 标签,执行脚本都会带来一定延时,最小化这一延时将显著改善页面总体性能。

处理外链 JavaScript 文件时,需考虑 HTTP 请求带来的额外性能开销。下载单个 100Kb 的文件比下载 5 个 20Kb 的文件更快,即减少页面中外链脚本的数量有助于提升性能。

大型网站或应用通常依赖多个 JavaScript 文件。可将多个文件合并为一个,仅需引用一个 <script> 标签即可减少性能消耗。文件合并工作可通过离线打包工具或实时在线服务完成。

需特别注意,将内嵌脚本置于引用外链样式表的 <link> 标签之后,会导致页面阻塞以等待样式表下载,这是为了确保内嵌脚本执行时能获取精确的样式信息。因此,建议避免将内嵌脚本紧跟在 <link> 标签后。

无阻塞脚本加载技术

无阻塞的脚本

在功能丰富的 Web 应用或大型网站上,仅减少 JavaScript 文件大小和限制 HTTP 请求数往往不可行。Web 应用功能越丰富,所需 JavaScript 代码越多。尽管下载单个较大的 JavaScript 文件仅产生一次 HTTP 请求,却会占用浏览器大量时间。为避免此情况,需通过特定技术逐步加载 JavaScript 文件,从而在一定程度上避免阻塞浏览器。

无阻塞脚本的核心在于页面加载完成后再加载 JavaScript 代码,即在 window 对象的 onload 事件触发后再下载脚本。实现这一效果有多种方式。

defer 属性延迟加载

延迟加载脚本

HTML 4 为 <script> 标签定义了 defer 扩展属性。Defer 属性表明脚本不会修改 DOM,因此代码可安全延迟执行。该属性仅被 IE 4 和 Firefox 3.5 及以上版本支持,故非理想的跨浏览器解决方案。在不支持该属性的浏览器中,defer 属性会被忽略,<script> 标签按默认方式处理,即造成阻塞。但若目标浏览器支持,这仍是一个有用的解决方案。

defer 属性使用方法示例


<script type="text/javascript" src="script1.js" defer></script>

带有 defer 属性的 <script> 标签可置于文档任意位置。对应的 JavaScript 文件在页面解析到该标签时开始下载,但不执行,直到 DOM 加载完成(即 onload 事件触发前)才执行。带有 defer 属性的 JavaScript 文件下载时不阻塞浏览器其他进程,因此可与其他资源并行下载。

任何带有 defer 属性的 <script> 元素在 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 属性的 <script> 元素并非紧随第二个脚本后执行,而是在 onload 事件触发前被调用。

若目标浏览器仅限 Internet Explorer 和 Firefox 3.5,defer 脚本确实有用。若需支持跨领域的多种浏览器,则需更一致的实现方式。

async 属性异步加载

HTML 5 为 <script> 标签定义了新的扩展属性:async。其作用与 defer 类似,能异步加载和执行脚本,不阻塞页面加载。但需注意,有 async 属性时,JavaScript 脚本一旦下载完成即执行,因此可能不按原定顺序执行。若 JavaScript 脚本存在前后依赖,使用 async 可能导致错误。

动态脚本元素加载

动态脚本元素

文档对象模型(DOM)允许通过 JavaScript 动态创建 HTML 的几乎全部内容。<script> 元素与其他页面元素一样,可通过标准 DOM 函数轻松创建:

通过标准 DOM 函数创建<script>元素


var script = document.createElement ("script"); 
 script.type = "text/javascript"; 
 script.src = "script1.js"; 
 document.getElementsByTagName("head")[0].appendChild(script);

新的 <script> 元素加载 script1.js 源文件。该文件在元素添加到页面后即刻开始下载。此技术的重点在于:无论何处启动下载,文件的下载和运行均不阻塞其他页面处理过程。即使将这些代码置于 <head> 部分,也不会影响其余页面代码(仅占用用于下载文件的 HTTP 连接)。

当文件使用动态脚本节点下载时,返回的代码通常立即执行(Firefox 和 Opera 除外,它们会等待此前所有动态脚本节点执行完毕)。对于“自运行”类型的脚本,这一机制运行正常;但若脚本仅包含供其他脚本调用的接口,则会带来问题。此时,需跟踪脚本下载完成并确认其是否准备就绪。可使用动态 <script> 节点发出事件以获取相关信息。

Firefox、Opera、Chrome 和 Safari 3+ 会在 <script> 节点接收完成后发出 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 事件。<script> 元素有一个 readyState 属性,其值随下载外部文件的过程而改变。readyState 有五种取值:

微软文档指出,在 <script> 元素的生命周期中,readyState 的这些取值不一定全部出现,但未指明哪些取值总会被用到。实践中,我们最感兴趣的是“loaded”和“complete”状态。Internet Explorer 对这两个 readyState 值所表示的最终状态并不一致,有时 <script> 元素会得到“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 属性,并将 <script> 元素添加至页面。此 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 下载中最常用的模式,因其跨浏览器且简单易用。

XHR 对象注入脚本

使用 XMLHttpRequest(XHR)对象

此技术首先创建 XHR 对象,然后下载 JavaScript 文件,接着用动态 <script> 元素将 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 表示缓存响应)。若收到有效响应,则创建新的 <script> 元素,将其文本属性设置为从服务器接收到的 responseText 字符串。这实际上创建了一个带有内联代码的 <script> 元素。一旦新 <script> 元素被添加到文档,代码将被执行并准备使用。

该方法的主要优点是可下载不立即执行的 JavaScript 代码。由于代码返回在 <script> 标签之外(即不受 <script> 标签约束),下载后不会自动执行,从而允许推迟执行直至一切准备就绪。另一优点是,同样的代码在所有现代浏览器中均不会引发异常。

此方法最主要的限制是: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'); 
});

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章