关键词:异步加载(async loading),延迟加载(lazy loading),延迟执行(lazy execution),async 属性, defer 属性
<script src="http://yourdomain.com/script.js"></script>
2. 常见异步加载(Script DOM Element)
(function() { var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = 'http://yourdomain.com/script.js'; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); })();
异步加载又叫非阻塞,浏览器在下载执行 js 同时,还会继续进行后续页面的处理。
(function() { var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })();
(function() {var po = document.createElement("script"); po.type = "text/javascript"; po.async = true;po.src = "https://apis.google.com/js/plusone.js"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(po, s); })();
但是,这种加载方式在加载执行完之前会阻止 onload 事件的触发,而现在很多页面的代码都在 onload 时还要执行额外的渲染工作等,所以还是会阻塞部分页面的初始化处理。
3. onload 时的异步加载
(function() { function async_load(){ var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = 'http://yourdomain.com/script.js'; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); } if (window.attachEvent) window.attachEvent('onload', async_load); else window.addEventListener('load', async_load, false); })();
- DOMContentLoaded : 页面(document)已经解析完成,页面中的dom元素已经可用。但是页面中引用的图片、subframe可能还没有加载完。
- OnLoad:页面的所有资源都加载完毕(包括图片)。浏览器的载入进度在这时才停止。
function loadScript(url, callback){ var script = document.createElement_x("script") script.type = "text/javascript"; if (script.readyState){ //IE script.onreadystatechange = function(){ if (script.readyState == "loaded" || script.readyState == "complete"){ script.onreadystatechange = null; callback(); } }; } else { //Others: Firefox, Safari, Chrome, and Opera script.onload = function(){ callback(); }; } script.src = url; document.body.appendChild(script); }
5.异步加载的其它方法
由于Javascript的动态特性,还有很多异步加载方法:
- XHR Eval
- XHR Injection
- Script in Iframe
- Script Defer
- document.write Script Tag
- 还有一种方法是用 setTimeout 延迟0秒 与 其它方法组合。
XHR Eval :通过 ajax 获取js的内容,然后 eval 执行。
var xhrObj = getXHRObject(); xhrObj.onreadystatechange = function() { if ( xhrObj.readyState != 4 ) return; eval(xhrObj.responseText); }; xhrObj.open('GET', 'A.js', true); xhrObj.send('');
<span style="font-size: small;"> var xhr = new XMLHttpRequest(); xhr.open("get","load.js",true); xhr.onreadystatechange=function(){ if(readyState==4){ if(status>=200&&status<300||status==304){ var script = document.createElement("script"); script.type = "text/javascript"; script.src = xhr.responseText; document.body.appendChild(script); } } } xhr.send(null);
</span>
Script in Iframe:创建并插入一个iframe元素,让其异步执行 js
var iframe = document.createElement('iframe'); document.body.appendChild(iframe); var doc = iframe.contentWindow.document; doc.open().write('<body onload="insertJS()">'); doc.close();
GMail Mobile:页内 js 的内容被注释,所以不会执行,然后在需要的时候,获取script元素中 text 内容,去掉注释后 eval 执行。
<script type="text/javascript"> /* var ... */ </script>
详见参考资料中2010年的Velocity 大会 Steve Souders 和淘宝的那两个讲义。
二、async 和 defer 属性
<script src="file.js" defer>
</script>
或
<script type="text/javascript" defer="defer">
alert(document.getElementById("p1").firstChild.nodeValue);
</script>
- 延迟脚本执行,相当于将script标签放入页面body标签的底部,js脚本会在document的DOMContentLoaded之前执行
- 除IE和较新版本的Firefox外,其他浏览器并未支持, defer属性在IE 4.0中就实现了,超过13年了!Firefox 从 3.5 开始支持defer属性 。
<script src="file.js" async></script>
- async 属性仅适用于外部脚本(只有在使用 src 属性时)。
- 注释:有多种执行外部脚本的方法:
- 如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
- 如果不使用 async 且 defer="defer":脚本将在页面完成解析时执行
- 如果既不使用 async 也不使用 defer:在浏览器继续解析页面之前,立即读取并执行脚本
- type 属性在HTML 4中是必须的,在HTML5中是可选的。
- async 属性是HTML5中新增的。
- 个别属性(xml:space)在HTML5中不支持。
- 没有 async 属性,script 将立即获取(下载)并执行,然后才继续后面的处理,这期间阻塞了浏览器的后续处理。
- 如果有 async 属性,那么 script 将被异步下载并执行,同时浏览器继续后续的处理。
- HTML4中就有了defer属性,它提示浏览器这个 script 不会产生任何文档元素(没有document.write),因此浏览器会继续后续处理和渲染。
- 如果没有 async 属性 但是有 defer 属性,那么script 将在页面parse之后执行。
- 如果同时设置了二者,那么 defer 属性主要是为了让不支持 async 属性的老浏览器按照原来的 defer 方式处理,而不是同步方式。
- 浏览器在下载完 js 的内容后就会立即对其解析和执行,不管是同步加载还是异步加载。
- 前面说的异步加载,解决的只是下载阶段的问题,但代码在下载后会立即执行。
- 而浏览器在解析执行 JS 阶段是阻塞任何操作的,这时的浏览器处于无响应状态。
<script src=“…”></script>
</head>
- 阻止了后续的下载;
- 在IE 6-7 中 script 是顺序下载的,而不是现在的 “并行下载、顺序执行” 的方式;
- 在下载和解析执行阶段阻止渲染(rendering);
...
<script src=“…”></script>
</body>
- 不阻止其它下载;
- 在IE 6-7 中 script 是顺序下载的;
- 在下载和解析执行阶段阻止渲染(rendering);
('script');
se.src = 'http://anydomain.com/A.js';
document.getElementsByTagName('head')
[0].appendChild(se);
这就是本文主要说的方式。
- 不阻止其它下载
- 在所有浏览器中,script都是并行下载
- 只在解析执行阶段阻止渲染(rendering)
- 异步下载 + 按需执行 (2010)
var se = new Image();
se.onload = registerScript();
se.src = 'http://anydomain.com/A.js';
把下载 js 与 解析执行 js 分离出来
- 不阻止其它下载;
- 在所有浏览器中,script都是并行下载;
- 不阻止渲染(rendering)直到真正需要时;
六、异步加载的问题
在异步加载的时候,无法使用 document.write 输出文档内容。
在同步模式下,document.write 是在当前 script 所在的位置输 出文档的。而在异步模式下,浏览器继续处理后续页面内容,根本无法确定 document.write 应该输出到什么位置,所以异步模式下 document.write 不可行。而到了页面已经 onload 之后,再执行 document.write 将导致当前页面的内容被清空,因为它会自动触发 document.open 方法。
实际上document.write的名声并不好,最好少用。
替代方法:
- 虽然异步加载不能用 document.write,但还是可以onload之后执行操作dom(创建dom或修改dom)的,这样可以实现一些自己的动态输出。比如要在页面异步创建一个浮动元素,这和它在页面中的位置就没关系了,只要创建出该dom元素添加到 document 中即可。
- 如果需要在固定位置异步生成元素的内容,那么可以在该固定位置设置一个dom元素作为目标,这样就知道位置了,异步加载之后就可以对这个元素进行修改。
下面介绍一个jquery方法
$(document).ready(function() { $('#btnLoad1.button').click(function() { $('#header').html("正在加载..."); //$('#load_content').load('http://jqueryui.com/', ); $('#load_content').hide().load('http://www.baidu.com', function(responseText, textStatus, XMLHttpRequest) { //所有状态成功,执行此函数,显示数据 //textStatus四种状态 success、error、notmodified、timeout if (textStatus == "error") { var msg = "错误: "; $('#header').html(msg + XMLHttpRequest.status + " " + XMLHttpRequest.statusText); } else if (textStatus == "timeout") { var msg = "操时: "; $('#header').html(msg + XMLHttpRequest.status + " " + XMLHttpRequest.statusText); } else { $('#header').html("加载完成"); $(this).fadeIn(); } }); }); });
HTML代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>JQuery - Load</title> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Expires" content="-1"> <mce:script type="text/javascript" src="js/jquery-1.3.2.min.js" mce_src="js/jquery-1.3.2.min.js"></mce:script> <mce:style type="text/css">
<!-- #header { margin-bottom: 1em; padding-bottom: 1em; border-bottom: 1px solid #eee; } .buttonListArea { float: left; 150px; padding-right: 10px; border-right: 1px solid #eee; margin-right: 10px; } .button{ cursor:pointer; } .buttonArea { margin: 10px; padding-bottom: 20px; } #load_content { float: left; 550px; text-align: center; } -->
</mce:style>
<style type="text/css" mce_bogus="1">
#header
{ margin-bottom: 1em; padding-bottom: 1em; border-bottom: 1px solid #eee; } .buttonListArea { float: left; width: 150px; padding-right: 10px; border-right: 1px solid #eee; margin-right: 10px; } .button{ cursor:pointer; } .buttonArea { margin: 10px; padding-bottom: 20px; } #load_content { float: left; width: 550px; text-align: center; } </style> </head> <body> <form id="form1" runat="server"> <div id="container"> <div id="header"> <h2>加载演示</h2> </div> <div class="buttonListArea"> <div class="buttonArea"> <div class="button" id="btnLoad1"> Load 1</div> </div> </div> <div id="load_content"> <h2>这是要被加载的区域</h2> </div> </div> </form> </body> </html>
七、JS 模块化管理
异步加载,需要将所有 js 内容按模块化的方式来切分组织,其中就存在依赖关系,而异步加载不保证执行顺序。
另外,namespace 如何管理 等相关问题。这部分已超出本文内容,可参考:
RequireJS 、 CommonJS 以及 王保平(淘宝)的 SeaJS 及其博客 。
八、JS最佳实践:
- 最小化 js 文件,利用压缩工具将其最小化,同时开启http gzip压缩。工具:
- 尽量不要放在 <head> 中,尽量放在页面底部,最好是</body>之前的位置
- 避免使用 document.write 方法
- 异步加载 js ,使用非阻塞方式,就是此文内容
- 尽量不直接在页面元素上使用 Inline Javascript,如onClick 。有利于统一维护和缓存处理。
参考: