JavaScript 同步与异步加载:HTML4/5 属性及动态插入原理详解

作者:半夏

时间:2026-10-26

浏览:622

本文详解 JavaScript 文件的同步与异步加载机制。对比 HTML4.01 与 HTML5 中 script 标签属性(如 defer、async)的差异,分析动态创建 script 节点、document.write 插入及 Image 预加载等实现方式。通过多浏览器测试日志,揭示不同加载策略对执行顺序、阻塞行为及回调触发的具体影响。

文章目录
HTML 4.01 的script属性

HTML4.01 与 HTML5 中 script 标签属性对比

charset:可选属性,用于指定引入代码的字符集。不过,大多数现代浏览器会忽略该值。


defer:布尔型可选属性,用于延迟脚本执行。其效果相当于将 script 标签放置在页面 body 底部,脚本将在 document 的 DOMContentLoaded 事件触发前执行。需要注意的是,除 IE 和较新版本的 Firefox 外,其他浏览器对该属性的支持有限。


language:该属性已被废弃,大部分浏览器会忽略此值。


src:可选属性,用于指定引入的外部代码文件路径,对文件后缀名没有限制。


type:必选属性,用于指定脚本的内容类型(MIME 类型)。在实际开发中,通常不显式指定该值,浏览器会默认将其解释为 text/javascript 类型并执行。



HTML5中的script属性:

在 HTML5 标准中,script 标签除了具备 HTML5 新定义的属性外,与 HTML4.01 相比移除了 language 属性,将 type 属性修改为可选(默认值为 text/javascript),并新增了一个 async 属性。


async:布尔型属性,用于定义脚本是否异步执行,取值可以是 true 或 false。


如果将 async 属性设置为 true,则会忽略 defer 属性。


异步执行的 JS 文件被假定为不使用 document.write() 向正在加载的 document 写入内容。因此,在异步加载执行的 JS 文件中,不应使用 document.write()。


动态引入 JS 文件的加载机制

除了 script 标签的属性外,页面引入 JS 文件的方式也会影响其加载和执行方式:


任何通过添加 script 节点(例如使用 appendChild(scriptNode))方式引入的 JS 文件都是异步执行的。需要注意的是,仅创建节点和设置 src 属性并不会加载 JS 文件,必须将节点插入 document 中(这与 img 标签的预加载机制不同)。


HTML 文件中 script 标签内的代码或通过 src 属性引用的 JS 文件中的代码,默认是同步加载和执行的。


HTML 文件中 script 标签内的代码使用 document.write() 方式引入的 JS 文件是异步执行的。


HTML 文件中 script 标签 src 属性所引用的 JS 文件代码内部,再使用 document.write() 方式引入的 JS 文件是同步执行的。


可以使用 Image 对象异步预加载 JS 文件,但需要注意的是,这种方式不会执行 JS 代码。



不要使用类似下面的做法,因为这样并不会发起加载 JS 文件的请求:


divNode.innerHTML = '<script src="xxx.js"></script>';


window.onload 事件会在 JS 文件加载完毕后才触发,即使该文件是异步加载的。


=====================================================

同步与异步加载的具体实现方式

1、


<script>


//同步加载执行的代码


</script>


2、


<script src="xx.js"></script> //同步加载执行xx.js中的代码


3、


<script>


document.write('<script src="xx.js"><\/script>'); //异步加载执行xx.js中的代码


</script>


4、


<script src="xx.js"></script>


xx.js中有下面代码:


代码如下:

 
document.write('<script src="11.js"><\/script>');
document.write('<script src="22.js"><\/script>');

则xx.js和11.js、22.js 都是同步加载和执行的。


如果 xx.js 、11.js 和 22.js 以插入 script 节点方式异步加载,则 11.js 和 22.js 是异步加载的,


如果 xx.js 以script 节点方式异步加载, 11.js 和 22.js 以 document.write(script) 方式加载,则 11.js 和 22.js 是同步加载的(经最新的浏览器测试, 在chrome 下,xx.j 异步加载执行已经无法使用 document.write() 向文档写入内容的 ,但是 firefox 和IE 却可以在 document 关闭之前写入(方法是在 html 中alert阻止文档关闭))


测试:在11.js中 alert()(不要用 for 循环,浏览器是单线程执行的,持续执行任何一段代码都会导致其余代码被阻塞) , 22.js 中 console.log() ,可以看到 22.js中的代码被阻塞


5、


下面这种方式,xx.js会在appendChild执行之后异步加载执行


代码如下:

 
var script = document.createElement("script");
script.setAttribute("src","xx.js");
documenrt.getElementsByTagName("head")[0].appendChild(script);

使用 Image 对象预加载 JS 文件

6、使用 Image 对象异步预加载 js 文件(不会被执行)


Image 的 src 被赋值时即发起请求,而且对文件类型不挑剔(图片也可能是有脚本动态创建的,比如验证码),因此可以将 js 文件的 url 赋给 image.src, js 加载之后被浏览器缓存.


代码如下:

 
var img = new Image();
img.onload = function(){ alert(1); } ; //由于返回的js文件 MIME 不是图片,onload回调函数并不会被触发
img.src = 'http://localhost/test/loadjs/try.2.js';
var s = document.createElement("script");
var h = document.getElementsByTagName("head")[0];
//执行 js
s.src=img.src;
h.appendChild(s);

封装通用的 JS 加载函数

一个加载 js 文件的 函数:


代码如下:

 
var loadJS = function(url,callback){
var head = document.getElementsByTagName('head');
if(head&&head.length){
head = head[0];
}else{
head = document.body;
} 
var script = document.createElement('script');
script.src = url;
script.type = "text/javascript";
head.appendChild( script);
script.onload = script.onreadystatechange = function(){
//script 标签,IE 下有 onreadystatechange 事件, w3c 标准有 onload 事件
//这些 readyState 是针对IE8及以下的,W3C 标准的 script 标签没有 onreadystatechange 和 this.readyState ,
//文件加载不成功 onload 不会执行,
//(!this.readyState) 是针对 W3C标准的, IE 9 也支持 W3C标准的 onload
if ((!this.readyState) || this.readyState == "complete" || this.readyState == "loaded" ){
callback();
} 
} //end onreadystatechange
} 

多浏览器加载行为测试与日志分析

对于第4点的测试(同步加载)(其中插入 alert 很容易看到加载时的阻塞)


tryjs.html


代码如下:

 
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="tryjs.js"
onload="if(!document.all){console.log('outer js callback, not IE');}"
onreadystatechange="console.log('outer js callback ',this.readyState,' IE');"></script>
<body>
</body>
</html>

tryjs.js


代码如下:

 
console.log('write begin');
document.write('<script src="try.1.js" onreadystatechange="console.log(\'file 1 callback \',this.readyState,\' IE\');" onload="if(!document.all){console.log(\'file 1 callback,NOT IE \');}"><\/script>');
document.write('<script src="try.2.js" onreadystatechange="console.log(\'file 2 callback \',this.readyState,\' IE\');" onload="if(!document.all){console.log(\'file 2 callback,NOT IE \');}"><\/script>');
console.log('write finished');

try.1.js


代码如下:

 
console.log('loadjs 1 begin');
console.log('loadjs 1 finished');

try.2.js


代码如下:

 
console.log('loadjs 2 begin');
console.log('loadjs 2 finished');

测试结果(file 2 和 file 1 的 callback complete 在IE7\8\9次序不确定)


IE 7:


日志: outer js callback loading IE


日志: outer js callback loaded IE


日志: write begin


日志: write finished


日志: outer js callback complete IE


日志: file 1 callback loading IE


日志: file 2 callback loading IE


日志: loadjs 1 begin


日志: loadjs 1 finished


日志: loadjs 2 begin


日志: loadjs 2 finished


日志: file 2 callback complete IE


日志: file 1 callback complete IE



IE8:


日志: outer js callback loading IE


日志: outer js callback loaded IE


日志: write begin


日志: write finished


日志: outer js callback complete IE


日志: file 1 callback loading IE


日志: file 2 callback loading IE


日志: loadjs 1 begin


日志: loadjs 1 finished


日志: loadjs 2 begin


日志: loadjs 2 finished


日志: file 2 callback complete IE


日志: file 1 callback complete IE



IE9:


日志: write begin


日志: write finished


日志: outer js callback complete IE


日志: file 1 callback loading IE


日志: file 2 callback loading IE


日志: loadjs 1 begin


日志: loadjs 1 finished


日志: loadjs 2 begin


日志: loadjs 2 finished


日志: file 1 callback complete IE


日志: file 2 callback complete IE



FIREFOX:


write begin


write finished


outer js callback, not IE


loadjs 1 begin


loadjs 1 finished


file 1 callback,NOT IE


loadjs 2 begin


loadjs 2 finished


file 2 callback,NOT IE



CHROME:


write begin


write finished


outer js callback, not IE


loadjs 1 begin


loadjs 1 finished


file 1 callback,NOT IE


loadjs 2 begin


loadjs 2 finished


file 2 callback,NOT IE

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章