高性能JavaScript异步加载-零度编程
高性能JavaScript异步加载
2023/7/6 10:13:32
脚本的阻塞性
在web开发中,针对JavaScript的性能的讨论,离不开它的阻塞性,一般情况下,当前JavaScript 在下载和运行时,后续的JavaScript引用、文档节点和样式都不能被浏览器处理,我们说JavaScript具有串行性或者阻塞性。这将导致用户只能等待才能看到最终页面,如果脚本过大,还可能影响用户体验。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>零度-分享编程之美</title>
<script type="text/javascript" src="script1.js"></script>
<script type="text/javascript" src="script2.js"></script>
</head>
<body>
这里是文档正文部分。
</body>
</html>
正如上面的文档结构,浏览器会首先处理head部分,下载script1.js->执行script1.js->下载script2.js->执行script2.js->后续文档处理->完毕,最后处理head下面的节点,在脚本未执行完毕时,后续工作都需等待,虽然在最新的浏览器中都支持并行下载js文件,但浏览器仍然无法并行执行,因为浏览器UI是单线程的,即是script1和script2能够同时下载,但他们也必须保持script1执行完毕后才能执行script2,最后才能渲染界面。
不阻塞的脚本
为了提高性能,我们通常会采用如下的做法:
1、合并文件:如果有多个script引用,我们将文件合并至单个,这样有助于建立一次TCP连接,即可下载。
2、压缩文件:将格式化的JavaScript代码压缩,这样可以删除js文件中的空白字符,从而减少文件大小。
3、文档末尾加载:将JavaScript引用写在
结束标签之前,因为整个文档正文已经加载完毕。
延期执行脚本
如果您一定要将script标签放置在head中,那么建议给script标签添加一个无值的defer属性,如下所示:
<script type="text/javascript" src="script1.js" defer></script>
<script type="text/javascript" src="script2.js" defer></script>
这样的代码将使得浏览器按照:下载script1.js->下载script2.js->文档加载呈现->执行script1.js->执行script2.js->完毕,当然下载仍然是阻塞性的,只是执行过程被放到了文档加载完毕之后,但defer属性并非所有浏览器都能够支持。
动态加载脚本
JavaScript的强大在于它能够动态操纵DOM元素,利用这点特点,可以在文档加载完毕后,动态创建script标签,方法封装如下。
<script>
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 { //其它浏览器状态判断
script.onload = function () {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
</script>
上面的代码是零度封装的函数,您可以在文档末尾调用loadScript函数动态加载脚本,该函数第一个参数url表示js文件地址,第二个参数callback表示脚本加载完毕后需要回调的自定义函数,该函数兼容所有浏览器,虽然可以动态加载脚本,如果需要加载多个js文件,浏览器可能无法保证加载的次序,如果您的脚本引用具有顺序性,可以使用如下的方式调用。
<script>
loadScript("script1.js", function () {
loadScript("script2.js", function () {
//script1.js和script2.js都已下载完毕
});
});
</script>
上面的代码首先会下载script1.js,在script1.js加载完毕的后回函数中下载script2.js,然后执行回调函数。
脚本动态注入
另外一种异步加载script的方式是使用AJAX,通过异步方式下载js文件,然后将js内容注入到script标签内,然后执行。
<script>
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);
</script>
上面的代码通过AJAX请求get方式下载script1.js文件,当HTTP状态返回正常时,将脚本内容追加到script标签内,注入脚本会立即执行。如果您使用jQuery编程,那么异步加载一个脚本将非常简单:
<script>
$.getScript("script1.js", function (data, status, jqxhr) {
//脚本加载完毕,执行其它操作
});
</script>
上面两种AJAX异步加载脚本的示例看上去很完美,但它有一个缺陷就是AJAX不能跨域,如果您所处的域名和脚本域名不在同一个域名之下,那么这种方案将不能够被采纳。针对AJAX跨域有一些解决方案,但不是本文讨论的范围,如果您想通过AJAX跨域访问,可学习关于JSONP的相关知识,它会告诉您如何进行AJAX跨域编程。
当然如果异步加载的脚本文件过多,脚本依赖性的管理将变得非常困难,模块化的JavaScript编程方式将越来越被开发者重视,类似于CommonJS、AMD、NodeJS、RequireJS和SeaJS的框架将被广大网站所使用,建议大家学习,异步编程,按需加载是当今编程的主题词。感谢阅读本文,希望对您有所帮助。