(本篇文章针对jQuery1.6.1版本)经过前两篇文章对jQuery异步回调机制的详细分析,关于jQuery如何实现异步回调机制的原理已经非常清楚了--将"回调函数"与"击发动作"两个步骤分开,这样可以先把回调函数作为子弹预先存储到弹夹中,由指定对象在"特定条件"实现后再击发子弹。异步对象的done、fail、then、always方法都是存储回调函数的,异步对象的resolveWith、resolve、rejectWith、reject都是执行击发动作的,(特殊调用时序下也可以由前面四个方法执行空仓挂机复位射击操作)。本篇来详细讲讲"特定条件"有哪些。
这个场景的"特定条件"是time out,也就是一个定时器经过设定的时间后触发"开枪射击",一个简单的例子即可说明。
这个场景的"特定条件"是document状态改变,只要浏览器在加载dom过程中状态发生改变时(不限于文档加载完成状态)都可以触发"击发动作",一个超大的html文档或者用破网速访问一个破网站时示例会比较清晰,这里给出一个简单的例子。
这个场景的"特定条件"是计算结束或者网络响应状态改变(失败、进行中、成功等),CPU计算的样例下面给出一个,后一种即ajax,关于ajax也单独讲解。
如果用类似的实现来类比jQuery.when这个方法,那么java.util.concurrent.CyclicBarrier无疑是最贴切的。其思想简而言之就是"只有当所有线程都到达闸门时才触发开闸放水的动作",复杂言之就是这个方法接受两把或以上的"手枪"配合内部一把"手枪"一起使用(这把内部的枪将要导出其傀儡接受子弹),当传递的多把手枪都被击发后(根据各自不同的使用场景)才可以击发最后的那把枪。这也就是为什么这个方法名字要叫when并需要配合增强异步对象傀儡的then/done/fail/always方法一起使用的原因了:"when A1 and A2 [and A3...] then B",只有A1到An这一系列"特定条件"实现了才可以触发B的执行。
这里不继续展开其内部原理了(涉及到复杂的闭包等),只列举一个简单的例子说明(实际组合使用以及参数调用和传递情况可以很复杂)。
1 window.setTimeout()方法触发
这个场景的"特定条件"是time out,也就是一个定时器经过设定的时间后触发"开枪射击",一个简单的例子即可说明。
var gun = jQuery._Deferred();
gun.done(function(){
console.info("time out,then execute this function! time is : ", new Date().getTime());
});
window.setTimeout(function(){
gun.resolve();
}, 5000);
console.info("now time is : ", new Date().getTime());
当然,为了说明参数和特定对象,我们可以再设计一个简单的例子。
var obj = {
p1:function(a){console.info("obj.p1,a is '",a,"'");},
p2:function(a,b){console.info("obj.p2,a is '",a,"', b is '", b, "'");}
};
var gun2 = jQuery.Deferred();
gun2.done(obj.p1, obj.p2, function(a,b){
this.p1(a);
this.p2(a,b);
});
window.setTimeout(function(){
gun2.resolveWith(obj, ["the article author is warhin.","who is warhin?","he is a diaosi!"]);
}, 5000);
console.info("now time is : ", new Date().getTime());
2 浏览器加载文档dom对象树状态改变时触发
这个场景的"特定条件"是document状态改变,只要浏览器在加载dom过程中状态发生改变时(不限于文档加载完成状态)都可以触发"击发动作",一个超大的html文档或者用破网速访问一个破网站时示例会比较清晰,这里给出一个简单的例子。
<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.1.min.js"></script>
<script type="text/javascript">
(function(){
var gun = jQuery.Deferred();
gun.done(function(){
var h1 = $("h1");
if (h1[0]) {
h1.bind("click", function(){
$("div").first().show();
});
}
});
if ( document.addEventListener ) {
document.addEventListener( "DOMContentLoaded", f, false );
} else if ( document.attachEvent ) {
document.attachEvent( "onreadystatechange", f );
}
function f() {
alert("document.readyState : "+document.readyState);
gun.resolve();
}
})();
</script>
</head>
<body>
<h1>点击我看看</h1>
<div style='display:none'>其实我早就潜伏在这里了......</div>
</body>
</html>
jQuery与dom加载事件相关的模块就是用的jQuery异步回调机制,后面专门开辟文章详细分析。
3 长时间CPU计算结束或网络交互响应时触发
这个场景的"特定条件"是计算结束或者网络响应状态改变(失败、进行中、成功等),CPU计算的样例下面给出一个,后一种即ajax,关于ajax也单独讲解。
var gun = jQuery.Deferred();
gun.done(function(){
console.info("终于计算结束了");
});
(function(){
for (var i = 0; i <10000; i++) {
(i+1) * (i+2);
}
console.info("trigger");
gun.resolve();
})();
4 探究jQuery.when
// Deferred helper
when: function( firstParam ) {
var args = arguments,
i = 0,
length = args.length,
count = length,
deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
firstParam :
jQuery.Deferred();
function resolveFunc( i ) {
return function( value ) {
args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
if ( !( --count ) ) {
// Strange bug in FF4:
// Values changed onto the arguments object sometimes end up as undefined values
// outside the $.when method. Cloning the object into a fresh array solves the issue
deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
}
};
}
if ( length > 1 ) {
for( ; i < length; i++ ) {
if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
args[ i ].promise().then( resolveFunc(i), deferred.reject );
} else {
--count;
}
}
if ( !count ) {
deferred.resolveWith( deferred, args );
}
} else if ( deferred !== firstParam ) {
deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
}
return deferred.promise();
}
网上几乎大部分人对这个函数的讲解都不是很清晰,很多同学(包括所谓大神)的思维都是"按照我喜欢的想使用的方式调用这个方法",而不是"按照这个方法的api说明来遵守调用",自以为是的结果肯定是缘木求鱼、刻舟求剑,甚至通篇解释都跟这个方法的内部实现、参数使用和设计原理毫无关系。
如果用类似的实现来类比jQuery.when这个方法,那么java.util.concurrent.CyclicBarrier无疑是最贴切的。其思想简而言之就是"只有当所有线程都到达闸门时才触发开闸放水的动作",复杂言之就是这个方法接受两把或以上的"手枪"配合内部一把"手枪"一起使用(这把内部的枪将要导出其傀儡接受子弹),当传递的多把手枪都被击发后(根据各自不同的使用场景)才可以击发最后的那把枪。这也就是为什么这个方法名字要叫when并需要配合增强异步对象傀儡的then/done/fail/always方法一起使用的原因了:"when A1 and A2 [and A3...] then B",只有A1到An这一系列"特定条件"实现了才可以触发B的执行。
这里不继续展开其内部原理了(涉及到复杂的闭包等),只列举一个简单的例子说明(实际组合使用以及参数调用和传递情况可以很复杂)。
var gun1 = jQuery.Deferred();
gun1.done(function(){
console.info("g1");
});
var gun2 = jQuery.Deferred();
gun2.done(function(){
console.info("g2");
});
var gun3 = jQuery.Deferred();
gun3.done(function(){
console.info("g3");
});
jQuery.when(gun1,gun2,gun3).done(function(){
console.info('最后成功!');
}).fail(function(){
console.info('最后失败!');
}).always(function(){
console.info('总是执行!');
});
window.setTimeout(function(){
console.info("gun1 triggered!");
gun1.resolve();
window.setTimeout(function(){
console.info("gun2 triggered!");
gun2.resolve();
}, 3000);
}, 5000);
jQuery.ajax("http://www.zhihu.com/").always(function(){
console.info("gun3 triggered!");
gun3.resolve();
});
最后再纠正一个观点,严格来说javascript本身应该并没有真正的异步机制(在它的单线程执行环境下),但是对其回调机制可以非常灵活的实现,比如jQuery这样实现的,所以这几篇文章的标题其实应该是--jQuery回调机制探究。
重复申明:未经许可,严禁转载!