高性能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的框架将被广大网站所使用,建议大家学习,异步编程,按需加载是当今编程的主题词。感谢阅读本文,希望对您有所帮助。