一. 为什么采用回调?
采用 Ajax 请求为例:
var data = ajax("http://xxx.xx.xx"); // 假设发送一个Ajax请求获取一些数据
console.log(data); // 通常是获取不到data的
- 因为 Ajax 请求一般是异步的,发送了请求之后,不会等待服务器响应结果而会继续执行后面的代码,而此时 console.log 中的 data 尚未获取到值。
- 如果采用同步 Ajax 请求,则会阻塞浏览器 I/O ,浏览器操作都会卡滞(如刷新,跳转),体验不好。
为了避免这种情况,所以要等到特定的条件完成,然后再执行特定的程序,而采用回调。
二. 嵌套回调(回调地狱)与链式回调
回调函数的缺陷之一,代码混乱,不好阅读识别。
- 当回调函数里面调用回调函数,这样一层一层的嵌套下去,通常称为回调地狱。例:
element.onclick = function handler(e){
setTimeout(function request(){
ajax("http://xxx.xxx.xx",function res(data){
if(data == "Data"){
handler();
}
else if(data == "noData"){
res();
}
})
},2000)
}
"等待元素点击事件触发,然后等待定时器启动,然后等待Ajax请求返回结果,之后可能又从头开始"
- 现在换一种编写方式实现上面的功能,链式调用。例:
element.onclick = function(){
handler();
}
function handler(){
setTimeout(request,2000);
}
function request(){
ajax("http://xxx.xxx.xx",res);
}
res(){
if(data == "Data"){
handler();
}
else if(data == "noData"){
res();
}
}
这两种回调的编写方式都有着共同的缺陷,那就是代码可读性不好,程序执行顺序不好追踪,如果说上面的代码好还看出来,实际开发中,代码里面还有会有其他程序调用,会对代码阅读追踪造成更大的困扰。
三. 回调的信任问题
回调函数有时会把自己执行一部分程序的控制权交给第三方,称为控制反转。例:
a();
ajax("http://xxx.xxx.xx",function(){
c();
})
b();
- a() 和 b() 的执行都在自身主程序的控制之下, c() 会在 Ajax 请求完成后触发,而 Ajax 何时请求完成,是由第三方控制的。
- 还有是这种模式一般不会出问题,但是有风险。如果 c() 里面的代码不是自己写的,而是第三方库里面的或同事写的,你期望Ajax请求之后 c() 里面某个功能调用一次,但实际有可能被返回的代码调用多次而发生不期望的结果。
简略的说,回调函数可能会出现以下情况:
- 调用回调过早 。
- 调用回调过晚(或不被调用)。
- 调用回调次数过少或过多 。
- 未能传递所需的环境和参数 。
- 吞掉可能出现的错误和异常。