javascript的延迟等待实现
这段时间遇到一个问题:使用ajax生成一个列表,然后使用sorttable.js对这个列表进行排序。问题在于:生成的列表还没出现,排序已经开始了,结果没有找到列表报错。解决方法:
function ifExist(table){ if(table.tBodies[0]==null) { setTimeout( function(){ifExist(table);}, 1000); } else { sorttable.makeSortable(table); } }
判断节点是否生成了,如果没有,那么就等待1秒,再循环执行。一切ok了!
最后,补充一下关于javascript同步和异步的问题(转载):
一、同步加载与异步加载的形式1. 同步加载我们平时最常使用的就是这种同步加载形式:<script src="http://yourdomain.com/script.js"></script>同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止了后续的解析,因此停止了后续的文件加载(如图像)、渲染、代码执行。js 之所以要同步执行,是因为 js 中可能有输出 document 内容、修改dom、重定向等行为,所以默认同步执行才是安全的。以前的一般建议是把<script>放在页面末尾</body>之前,这样尽可能减少这种阻塞行为,而先让页面展示出来。简单说:加载的网络 timeline 是瀑布模型,而异步加载的 timeline 是并发模型。
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); })();(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 时还要执行额外的渲染工作等,所以还是会阻塞部分页面的初始化处理。(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); })(); 这和前面的方式差不多,但关键是它不是立即开始异步加载 js ,而是在 onload 时才开始异步加载。这样就解决了阻塞 onload 事件触发的问题。补充:DOMContentLoaded 与 OnLoad 事件DOMContentLoaded : 页面(document)已经解析完成,页面中的dom元素已经可用。但是页面中引用的图片、subframe可能还没有加载完。OnLoad:页面的所有资源都加载完毕(包括图片)。浏览器的载入进度在这时才停止。这两个时间点将页面加载的timeline分成了三个阶段。4.异步加载的其它方法
由于Javascript的动态特性,还有很多异步加载方法:XHR Eval XHR InjectionScript in IframeScript Deferdocument.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(''); 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 属性1. defer 属性
<script src="file.js" defer></script>defer属性声明这个脚本中将不会有 document.write 或 dom 修改。浏览器将会并行下载 file.js 和其它有 defer 属性的script,而不会阻塞页面后续处理。defer属性在IE 4.0中就实现了,超过13年了!Firefox 从 3.5 开始支持defer属性 。注:所有的defer 脚本保证是按顺序依次执行的。2. async 属性
<script src="file.js" async></script>async属性是HTML5新增的。作用和defer类似,但是它将在下载后尽快执行,不能保证脚本会按顺序执行。它们将在onload 事件之前完成。Firefox 3.6、Opera 10.5、IE 9 和 最新的Chrome 和 Safari 都支持 async 属性。可以同时使用 async 和 defer,这样IE 4之后的所有 IE 都支持异步加载。3. 详细解释<script> 标签在 HTML 4.01 与 HTML5 的区别:type 属性在HTML 4中是必须的,在HTML5中是可选的。async 属性是HTML5中新增的。个别属性(xml:space)在HTML5中不支持。说明:
<head> <script src=“…”></script> </head>
阻止了后续的下载;在IE 6-7 中 script 是顺序下载的,而不是现在的 “并行下载、顺序执行” 的方式;在下载和解析执行阶段阻止渲染(rendering);2. script 放在页面底部(2007)
... <script src=“…”></script> </body>不阻止其它下载;在IE 6-7 中 script 是顺序下载的;在下载和解析执行阶段阻止渲染(rendering);3. 异步加载script(2009)
var se = document.createElement ('script'); se.src = 'http://anydomain.com/A.js'; document.getElementsByTagName('head') [0].appendChild(se);这就是本文主要说的方式。不阻止其它下载;在所有浏览器中,script都是并行下载;只在解析执行阶段阻止渲染(rendering);4. 异步下载 + 按需执行 (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的名声并不好,最好少用。替代方法:1. 虽然异步加载不能用 document.write,但还是可以onload之后执行操作dom(创建dom或修改dom)的,这样可以实现一些自己的动态输出。比如要在页面异步创建一个浮动元素,这和它在页面中的位置就没关系了,只要创建出该dom元素添加到 document 中即可。2. 如果需要在固定位置异步生成元素的内容,那么可以在该固定位置设置一个dom元素作为目标,这样就知道位置了,异步加载之后就可以对这个元素进行修改。六、JS 模块化管理异步加载,需要将所有 js 内容按模块化的方式来切分组织,其中就存在依赖关系,而异步加载不保证执行顺序。另外,namespace 如何管理 等相关问题。这部分已超出本文内容,可参考:RequireJS 、CommonJS 以及 王保平(淘宝)的SeaJS 及其博客 。七、JS最佳实践:1. 最小化 js 文件,利用压缩工具将其最小化,同时开启http gzip压缩。工具:2. 尽量不要放在 <head> 中,尽量放在页面底部,最好是</body>之前的位置3. 避免使用 document.write 方法4. 异步加载 js ,使用非阻塞方式,就是此文内容。5. 尽量不直接在页面元素上使用 Inline Javascript,如onClick 。有利于统一维护和缓存处理。参考资料:Lazy Loading Asyncronous JavascriptLoad Non-blocking JavaScript with HTML5 Async and Defer2010年 Velocity China 上的两个讲义:Steve Souders(Google)的 Even Faster Web Sites (pdf)李穆(淘宝)的 第三方广告代码稳定性和性能优化实战 (pdf)