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="yourdomain/script.js"></script>
同步模式,⼜称阻塞模式,会阻⽌浏览器的后续处理,停⽌了后续的解析,因此停⽌了后续的⽂件加载(如图像)、渲染、代码执⾏。
js 之所以要同步执⾏,是因为 js 中可能有输出 document 内容、修改dom、重定向等⾏为,所以默认同步执⾏才是安全的。
以前的⼀般建议是把<script>放在页⾯末尾</body>之前,这样尽可能减少这种阻塞⾏为,⽽先让页⾯展
⽰出来。
简单说:加载的⽹络 timeline 是瀑布模型,⽽异步加载的 timeline 是并发模型。
2. 常见异步加载(Script DOM Element)
(function() {
var s = ateElement('script');
s.async = true;
s.src ='yourdomain/script.js';
var x = ElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
异步加载⼜叫⾮阻塞,浏览器在下载执⾏ js 同时,还会继续进⾏后续页⾯的处理。
这种⽅法是在页⾯中<script>标签内,⽤ js 创建⼀个 script 元素并插⼊到 document 中。这样就做到了⾮阻塞的下载 js 代码。
async属性是HTML5中新增的异步⽀持,见后⽂解释,加上好(不加也不影响)。
此⽅法被称为 Script DOM Element 法,不要求 js 同源。
将js代码包裹在匿名函数中并⽴即执⾏的⽅式是为了保护变量名泄露到外部可见,这是很常见的⽅式,尤其是在 js 库中被普遍使⽤。
例如 Google Analytics 和 Google+ Badge 都使⽤了这种异步加载代码:
(function() {
var ga = ateElement('script'); ga.type ='text/javascript'; ga.async = true;
ga.src =('https:'== document.location.protocol ? 'ssl' : 'www')+'.google-analytics/ga.js';
var s = ElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
(function()
{var po = ateElement("script");
var s = ElementsByTagName("script")[0];
s.parentNode.insertBefore(po, s);
})();
但是,这种加载⽅式在加载执⾏完之前会阻⽌ onload 事件的触发,⽽现在很多页⾯的代码都在 onload 时还要执⾏额外的渲染⼯作等,所以还是会阻塞部分页⾯的初始化处理。
3. onload 时的异步加载
(function() {
function async_load(){
var s = ateElement('script');
s.async = true;
s.src ='yourdomain/script.js';
var x = ElementsByTagName('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 Injection
Script in Iframe
Script Defer
document.write Script Tag
还有⼀种⽅法是⽤ setTimeout 延迟0秒与其它⽅法组合。
XHR Eval :通过 ajax 获取js的内容,然后 eval 执⾏。
var xhrObj = getXHRObject();
function() {
if( adyState !=4)return;
sponseText);
注册页面js特效};
xhrObj.open('GET','A.js', true);
xhrObj.send('');
Script in Iframe:创建并插⼊⼀个iframe元素,让其异步执⾏ js 。
var iframe = ateElement('iframe');
document.body.appendChild(iframe);
var doc = tWindow.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中不⽀持。
说明:
1. 没有 async 属性,script 将⽴即获取(下载)并执⾏,然后才继续后⾯的处理,这期间阻塞了浏览器的后续处理。
2. 如果有 async 属性,那么 script 将被异步下载并执⾏,同时浏览器继续后续的处理。
3. HTML4中就有了defer属性,它提⽰浏览器这个 script 不会产⽣任何⽂档元素(没有document.write),因此浏览器会继续后续处理和
渲染。
4. 如果没有 async 属性但是有 defer 属性,那么script 将在页⾯parse之后执⾏。
5. 如果同时设置了⼆者,那么 defer 属性主要是为了让不⽀持 async 属性的⽼浏览器按照原来的 defer ⽅式处理,⽽不是同步⽅式。
另参见官⽅说明:
个⼈补充:
既然 HTML5 中已经⽀持异步加载,为什么还要使⽤前⾯推荐的那种⿇烦(动态创建 script 元素)的⽅式?
答:为了兼容尚不⽀持 async ⽼浏览器。如果将来所有浏览器都⽀持了,那么直接在script中加上async 属性是最简单的⽅式。
三、延迟加载(lazy loading)
前⾯解决了异步加载(async loading)问题,再谈谈什么是延迟加载。
延迟加载:有些 js 代码并不是页⾯初始化的时候就⽴刻需要的,⽽稍后的某些情况才需要的。延迟加载就是⼀开始并不加载这些暂时不⽤的js,⽽是在需要的时候或稍后再通过js 的控制来异步加载。
也就是将 js 切分成许多模块,页⾯初始化时只加载需要⽴即执⾏的 js ,然后其它 js 的加载延迟到第⼀次需要⽤到的时候再加载。
特别是页⾯有⼤量不同的模块组成,很多可能暂时不⽤或根本就没⽤到。
就像图⽚的延迟加载,在图⽚出现在可视区域内时(在滚动条下拉)才加载显⽰图⽚。
四、script 的两阶段加载与延迟执⾏(lazy execution)
JS的加载其实是由两阶段组成:下载内容(download bytes)和执⾏(parse and execute)。
浏览器在下载完 js 的内容后就会⽴即对其解析和执⾏,不管是同步加载还是异步加载。
前⾯说的异步加载,解决的只是下载阶段的问题,但代码在下载后会⽴即执⾏。
⽽浏览器在解析执⾏ JS 阶段是阻塞任何操作的,这时的浏览器处于⽆响应状态。
我们都知道通过⽹络下载 script  需要明显的时间,但容易忽略了第⼆阶段,解析和执⾏也是需要时间的。script的解析和执⾏所花的时间⽐我们想象的要多,尤其是script  很多很⼤的时候。有些是需要⽴刻执⾏,⽽有些则不需要(⽐如只是在展⽰某个界⾯或执⾏某个操作时才需要)。
这些script 可以延迟执⾏,先异步下载缓存起来,但不⽴即执⾏,⽽是在第⼀次需要的时候执⾏⼀次。
利⽤特殊的技巧可以做到下载与执⾏的分离 (再次感谢 javascript 的动态特性)。⽐如将 JS 的内容作为 Image或 object 对象加载缓存起来,所以就不会⽴即执⾏了,然后在第⼀次需要的时候再执⾏。
此部分的更多解释请查看末尾参考资料中 ControlJS 的相关链接。
⼩技巧:
1. 模拟较长的下载时间:
写个后端脚本,让其 sleep ⼀定时间。如在 jsp 中 Thread.sleep(5000); ,这样5秒后才能收到内容。
2. 模拟较长的 js 代码执⾏时间(因为这步⼀般⽐较快不容易观察到):
var t_start = Number(new Date());
while ( t_start + 5000 > Number(new Date()) ) {}
这个代码将使 js 执⾏5秒才能完成!
五、script 标签使⽤的历史
1. script 放在 HEAD 中
<head>
<script src=“…”></script>
</head>
2. script 放在页⾯底部(2007)
...
<script src=“…”></script>
</body>
不阻⽌其它下载;
在IE 6-7 中 script 是顺序下载的;
在下载和解析执⾏阶段阻⽌渲染(rendering);
3. 异步加载script(2009)
var se = ateElement
('script');
se.src ='anydomain/A.js';
[0].appendChild(se);
这就是本⽂主要说的⽅式。
不阻⽌其它下载;
在所有浏览器中,script都是并⾏下载;
只在解析执⾏阶段阻⽌渲染(rendering);
4. 异步下载 + 按需执⾏ (2010)
var se =new Image();
se.src ='anydomain/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 如何管理等相关问题。这部分已超出本⽂内容,可参考:
、以及王保平(淘宝)的及其。
七、JS最佳实践:
1. 最⼩化 js ⽂件,利⽤压缩⼯具将其最⼩化,同时开启http gzip压缩。⼯具:
2. 尽量不要放在 <head> 中,尽量放在页⾯底部,最好是</body>之前的位置
3. 避免使⽤ document.write ⽅法
4. 异步加载 js ,使⽤⾮阻塞⽅式,就是此⽂内容。
5. 尽量不直接在页⾯元素上使⽤ Inline Javascript,如onClick 。有利于统⼀维护和缓存处理。
参考资料:
2010年 Velocity China 上的两个讲义:
Steve Souders(Google)的 (pdf)
李穆(淘宝)的 (pdf)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。