详解 JavaScript 装载与执行机制及异步加载方案

作者:函数茶馆

时间:2026-10-28

浏览:572

本文深入解析 JavaScript 在浏览器中的装载与执行特性,指出其串行加载及阻塞渲染的默认行为。通过对比 document.write、defer/async 属性、动态 DOM 创建及事件绑定等多种异步加载方案,分析各自的优缺点与浏览器兼容性,并探讨预加载与按需执行的实现思路,帮助开发者优化页面性能。

文章目录

JavaScript 装载与执行机制

首先探讨 JavaScript 的装载与执行机制。浏览器处理 JavaScript 时具备两个核心特征:一是脚本载入后立即执行,二是执行过程中会阻塞页面后续内容的渲染及其他资源的下载。因此,当页面引入多个 JS 文件时,浏览器会按顺序串行加载并依次执行这些脚本。

由于 JavaScript 常涉及对 HTML DOM 树的操作,浏览器通常不会像并行下载 CSS 文件那样并行加载 JS 文件,这是由 JS 的特殊性决定的。若 JS 试图操作尚未加载完成的后续 DOM 元素,浏览器通常会报错提示对象未找到,因为 JS 执行时后续 HTML 被阻塞,DOM 树中尚不存在对应的节点。

传统加载方式及其局限性

传统方式

因此,当你在代码中编写如下结构时:

代码如下:

<script type="text/javascript"
src="http://coolshell.cn/asyncjs/alert.js"></script>

通常情况下,位于 <head> 标签内的 <script> 标签会阻塞后续资源的加载以及整个页面的生成。为此,我专门制作了一个示例供参考:示例一。请注意,我的 alert.js 文件中仅包含一句代码:alert("hello world"),这能更直观地展示 JavaScript 如何阻塞后续内容。

这就是为什么许多网站将 JavaScript 代码放置在网页末尾,或者利用 window.onload 或 document.ready 等事件处理函数的原因。

此外,鉴于绝大多数 JavaScript 代码无需等待页面加载即可运行,我们需要实现异步加载功能。那么,如何实现异步加载呢?

document.write 方式

你可能认为使用 document.write() 可以解决不阻塞的问题。确实,在同一个 <script> 标签内,使用 document.write 插入 <script> 标签后,后续代码可以执行。然而,对于整个页面而言,这依然会造成阻塞。下面是一段测试代码:

代码如下:

<script type="text/javascript" language="javascript">
function loadjs(script_filename) {
document.write('<' + 'script language="javascript" type="text/javascript"');
document.write(' src="' + script_filename + '">');
document.write('<'+'/script'+'>');
alert("loadjs() exit...");
    }
var script = 'http://coolshell.cn/asyncjs/alert.js';
loadjs(script);
alert("loadjs() finished!");
</script>
<script type="text/javascript" language="javascript">
alert("another block");
</script>

你认为 alert 弹出的顺序是什么?你可以在不同浏览器中尝试。相关的测试页面见:示例二。

defer 与 async 属性解析

script 的 defer 和 async 属性

IE 自 IE6 起便支持 defer 标签,例如:

代码如下:

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

对于 IE 浏览器,该标签会使其并行下载 JS 文件,并将执行推迟到整个 DOM 加载完毕(DOMContentLoaded)之后。多个带有 defer 属性的 <script> 标签在执行时也会按照其在文档中出现的顺序运行。最重要的是,加上 defer 属性后,<script> 不会阻塞后续 DOM 的渲染。但由于 defer 主要是 IE 专用,因此使用较少。

标准 HTML5 引入了一个用于异步加载 JavaScript 的属性:async。无论赋予它何种值,只要该属性存在,浏览器就会异步加载 JS 文件。然而,async 异步加载存在一个严重问题,即它严格遵循“载入后马上执行”的原则。因此,虽然它不阻塞页面渲染,但你无法控制其执行的顺序和时机。你可以查看此示例以体会差异。

支持 async 属性的浏览器包括:Firefox 3.6+、Chrome 8.0+、Safari 5.0+、IE 10+,Opera 尚不支持(来源参考此处)。因此,这种方法并非完美,因为并非所有浏览器都支持。

动态 DOM 创建与 JSONP

动态创建 DOM 方式

这种方式可能是应用最为广泛的。

代码如下:

function loadjs(script_filename) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', script_filename);
script.setAttribute('id', 'coolshell_script_id');
 
script_id = document.getElementById('coolshell_script_id');
if(script_id){
document.getElementsByTagName('head')[0].removeChild(script_id);
    }
document.getElementsByTagName('head')[0].appendChild(script);
}
 
var script = 'http://coolshell.cn/asyncjs/alert.js';
loadjs(script);

这种方式几乎已成为标准的异步加载 JS 文件方案,演示请参考:示例三。该方式还衍生出了 JSONP 技术,即为 script 的 src 指定某个后端脚本(如 PHP),该 PHP 返回一个 JavaScript 函数,其参数为 JSON 字符串,从而调用我们预先定义的 JavaScript 函数。你可以查看此示例:t.js(这是我之前在微博征集的一个异步 AJAX 调用小例子)。

按需异步加载与事件绑定

按需异步加载 JS

上述 DOM 方式示例解决了异步加载 JavaScript 的问题,但未解决按指定时机运行的问题。因此,只需将上述 DOM 方式绑定到某个事件即可。

例如:

绑定在 window.load 事件上——示例四

请务必比较示例四和示例三在执行上的差异。在这两个示例中,我都专门使用了代码高亮的 JavaScript 脚本,观察代码高亮脚本与 alert.js 的执行情况,你就能明白其中的不同。

代码如下:

window.load = loadjs("http://coolshell.cn/asyncjs/alert.js")

绑定在特定事件上——示例五

代码如下:

<p style="cursor: pointer" onclick="LoadJS()">Click to load alert.js </p>

此示例非常简单。当你点击某个 DOM 元素时,才会真正加载我们的 alert.js。

预加载与执行控制的 Hack 方案

更多

然而,绑定在特定事件上似乎有些过头,因为只有点击时才会真正下载 JS,这会导致速度过慢。至此,我们要抛出终极问题——我们希望异步地将 JS 文件下载到用户本地,但不执行,仅当我们需要执行时才去执行。

如果能有以下方式就好了:

代码如下:

var script = document.createElement("script");
script.noexecute = true;
script.src = "alert.js";
document.body.appendChild(script);
 
//后面我们可以这么干
script.execute();

遗憾的是,这只是一个美好的梦想,当前的 JavaScript 还比较原始,这个“JS 梦”尚未实现。

因此,程序员只能使用 hack 方式来实现。

有的程序员使用非标准的 script type 来缓存 JavaScript,例如:

代码如下:

<script type=cache/script src="./alert.js"></script>

由于“cache/script”无法被浏览器解析,浏览器不会将其作为 JavaScript 执行,但仍会下载 JS 文件,从而达成目的。遗憾的是,WebKit 严格遵循 HTML 标准——对于这种不认识的类型,直接删除,不做任何处理。于是,我们的梦想再次破灭。

因此,我们需要再次进行 hack,就像多年前预加载图片那样,我们可以利用 object 标签(也可以使用 iframe 标签),于是我们有以下代码:

代码如下:

function cachejs(script_filename){
var cache = document.createElement('object');
cache.data = script_filename;
cache.id = "coolshell_script_cache_id";
cache.width = 0;
cache.height = 0;
document.body.appendChild(cache);
}

然后,在页面最后调用该函数。请参考相关示例:示例六

在 Chrome 下按 Ctrl+Shift+I,切换到 Network 页面,你可以看到 alert.js 已被下载但未执行。随后,我们使用示例五的方式,由于浏览器端已有缓存,不会再从服务器下载 alert.js,从而保证执行速度。

关于这种预加载技术,你应该不会陌生。你还可以使用 Ajax 方式,例如:

代码如下:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'new.js');
xhr.send('');

到这里我就不再多说,也不提供示例,大家可以自行尝试。

最后再提两个 JS 库,一个是 ControlJS,另一个是 HeadJS,它们专门用于异步加载 JavaScript 文件。

以上就是所有内容,希望大家阅读后能对 JavaScript 的加载与执行以及相关技术有所了解。同时,也期望各位前端高手不吝赐教!

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章