高性能JavaScript异步加载

2023/7/6 02:13:32

"### 脚本的阻塞性

在web开发中,针对JavaScript的性能的讨论,离不开它的阻塞性,一般情况下,当前JavaScript 在下载和运行时,后续的JavaScript引用、文档节点和样式都不能被浏览器处理,我们说JavaScript具有串行性或者阻塞性。这将导致用户只能等待才能看到最终页面,如果脚本过大,还可能影响用户体验。

<span style=color: blue;><<span style=color: maroon;>!DOCTYPE <span style=color: red;>html<span style=color: blue;>> <<span style=color: maroon;>html <span style=color: red;>lang<span style=color: blue;>=en <span style=color: red;>xmlns<span style=color: blue;>=http://www.w3.org/1999/xhtml> <<span style=color: maroon;>head<span style=color: blue;>> <<span style=color: maroon;>title<span style=color: blue;>><span style=color: black;>零度-分享编程之美<span style=color: blue;></<span style=color: maroon;>title<span style=color: blue;>> <<span style=color: maroon;>script <span style=color: red;>type<span style=color: blue;>=text/javascript <span style=color: red;>src<span style=color: blue;>=script1.js></<span style=color: maroon;>script<span style=color: blue;>> <<span style=color: maroon;>script <span style=color: red;>type<span style=color: blue;>=text/javascript <span style=color: red;>src<span style=color: blue;>=script2.js></<span style=color: maroon;>script<span style=color: blue;>> <span style=color: blue;></<span style=color: maroon;>head<span style=color: blue;>> <<span style=color: maroon;>body<span style=color: blue;>> <span style=color: black;>这里是文档正文部分。 <span style=color: blue;></<span style=color: maroon;>body<span style=color: blue;>> </<span style=color: maroon;>html<span style=color: blue;>>

正如上面的文档结构,浏览器会首先处理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属性,如下所示:

<span style=color: blue;><<span style=color: maroon;>script <span style=color: red;>type<span style=color: blue;>=text/javascript <span style=color: red;>src<span style=color: blue;>=script1.js <span style=color: red;>defer<span style=color: blue;>></<span style=color: maroon;>script<span style=color: blue;>> <<span style=color: maroon;>script <span style=color: red;>type<span style=color: blue;>=text/javascript <span style=color: red;>src<span style=color: blue;>=script2.js <span style=color: red;>defer<span style=color: blue;>></<span style=color: maroon;>script<span style=color: blue;>>

这样的代码将使得浏览器按照:下载script1.js->下载script2.js->文档加载呈现->执行script1.js->执行script2.js->完毕,当然下载仍然是阻塞性的,只是执行过程被放到了文档加载完毕之后,但defer属性并非所有浏览器都能够支持。

动态加载脚本

JavaScript的强大在于它能够动态操纵DOM元素,利用这点特点,可以在文档加载完毕后,动态创建script标签,方法封装如下。

<span style=color: blue;><<span style=color: maroon;>script<span style=color: blue;>> function <span style=color: black;>loadScript(url, callback) { <span style=color: blue;>var <span style=color: black;>script = document.createElement(<span style=color: #a31515;>script<span style=color: black;>) script.type = <span style=color: #a31515;>text/javascript<span style=color: black;>; <span style=color: blue;>if <span style=color: black;>(script.readyState) { <span style=color: green;>//IE浏览器状态判断 <span style=color: black;>script.onreadystatechange = <span style=color: blue;>function <span style=color: black;>() { <span style=color: blue;>if <span style=color: black;>(script.readyState == <span style=color: #a31515;>loaded <span style=color: black;>|| script.readyState == <span style=color: #a31515;>complete<span style=color: black;>) { script.onreadystatechange = <span style=color: blue;>null<span style=color: black;>; callback(); } }; } <span style=color: blue;>else <span style=color: black;>{ <span style=color: green;>//其它浏览器状态判断 <span style=color: black;>script.onload = <span style=color: blue;>function <span style=color: black;>() { callback(); }; } script.src = url; document.getElementsByTagName(<span style=color: #a31515;>head<span style=color: black;>)[0].appendChild(script); } <span style=color: blue;></<span style=color: maroon;>script<span style=color: blue;>>

上面的代码是零度封装的函数,您可以在文档末尾调用loadScript函数动态加载脚本,该函数第一个参数url表示js文件地址,第二个参数callback表示脚本加载完毕后需要回调的自定义函数,该函数兼容所有浏览器,虽然可以动态加载脚本,如果需要加载多个js文件,浏览器可能无法保证加载的次序,如果您的脚本引用具有顺序性,可以使用如下的方式调用。

<span style=color: blue;><<span style=color: maroon;>script<span style=color: blue;>> <span style=color: black;>loadScript(<span style=color: #a31515;>script1.js<span style=color: black;>, <span style=color: blue;>function <span style=color: black;>() { loadScript(<span style=color: #a31515;>script2.js<span style=color: black;>, <span style=color: blue;>function <span style=color: black;>() { <span style=color: green;>//script1.js和script2.js都已下载完毕 <span style=color: black;>}); }); <span style=color: blue;></<span style=color: maroon;>script<span style=color: blue;>>

上面的代码首先会下载script1.js,在script1.js加载完毕的后回函数中下载script2.js,然后执行回调函数。

脚本动态注入

另外一种异步加载script的方式是使用AJAX,通过异步方式下载js文件,然后将js内容注入到script标签内,然后执行。

<span style=color: blue;><<span style=color: maroon;>script<span style=color: blue;>> var <span style=color: black;>xhr = <span style=color: blue;>new <span style=color: black;>XMLHttpRequest(); xhr.open(<span style=color: #a31515;>get<span style=color: black;>, <span style=color: #a31515;>script1.js<span style=color: black;>, <span style=color: blue;>true<span style=color: black;>); xhr.onreadystatechange = <span style=color: blue;>function <span style=color: black;>() { <span style=color: blue;>if <span style=color: black;>(xhr.readyState == 4) { <span style=color: blue;>if <span style=color: black;>(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { <span style=color: blue;>var <span style=color: black;>script = document.createElement(<span style=color: #a31515;>script<span style=color: black;>); script.type = <span style=color: #a31515;>text/javascript<span style=color: black;>; script.text = xhr.responseText; document.body.appendChild(script); } } }; xhr.send(<span style=color: blue;>null<span style=color: black;>); <span style=color: blue;></<span style=color: maroon;>script<span style=color: blue;>>

上面的代码通过AJAX请求get方式下载script1.js文件,当HTTP状态返回正常时,将脚本内容追加到script标签内,注入脚本会立即执行。如果您使用jQuery编程,那么异步加载一个脚本将非常简单:

<span style=color: blue;><<span style=color: maroon;>script<span style=color: blue;>> <span style=color: black;>$.getScript(<span style=color: #a31515;>script1.js<span style=color: black;>, <span style=color: blue;>function <span style=color: black;>(data, status, jqxhr) { <span style=color: green;>//脚本加载完毕,执行其它操作 <span style=color: black;>}); <span style=color: blue;></<span style=color: maroon;>script<span style=color: blue;>>

上面两种AJAX异步加载脚本的示例看上去很完美,但它有一个缺陷就是AJAX不能跨域,如果您所处的域名和脚本域名不在同一个域名之下,那么这种方案将不能够被采纳。针对AJAX跨域有一些解决方案,但不是本文讨论的范围,如果您想通过AJAX跨域访问,可学习关于JSONP的相关知识,它会告诉您如何进行AJAX跨域编程。

当然如果异步加载的脚本文件过多,脚本依赖性的管理将变得非常困难,模块化的JavaScript编程方式将越来越被开发者重视,类似于CommonJS、AMD、NodeJS、RequireJS和SeaJS的框架将被广大网站所使用,建议大家学习,异步编程,按需加载是当今编程的主题词。感谢阅读本文,希望对您有所帮助。"