本文深入解析JavaScript回调函数的核心概念,通过同步与异步代码示例说明其执行机制。重点探讨在Ajax请求、DOM事件及Node.js中的实际应用,对比轮询与回调的性能差异,并提供防止错误的最佳实践建议,帮助开发者掌握这一关键设计模式。
回调函数的基本概念
将函数作为参数传递给另一个函数,该被传入的函数即被称为回调函数。
在大型项目开发中,常出现A层与B层由不同人员协同开发的情况。若A层负责功能funA,B层负责funcB,当B层需要调用A层模块的数据时,B层人员会向A层人员提出接口需求,要求提供满足特定条件数据。
A层人员只需负责提供数据,而数据的展示与后续处理则由B层决定。由于B层无法为每个需求都定制独立接口,因此B层会提供一个通用的接口规范。B层获取数据后,编写相应的函数进行展示与处理。
这种协作模式意味着,你只需关注对获取到的数据进行操作,而无需关心他人获取或构建数据的具体方式。在此场景下,使用回调函数是最佳选择。
从本质上讲,回调是一种设计模式,jQuery及其他主流框架的设计原则均遵循了这一模式。
同步与异步回调示例
以下是一个在同步(阻塞)环境中使用回调的示例,旨在确保func1代码执行完毕后,再执行func2。
具体代码如下:
var func1=function(callback){
//do something.
(callback && typeof(callback) === "function") && callback();
}
func1(func2);
var func2=function(){
}
以下是异步回调的示例:
具体代码如下:
$(document).ready(callback);
$.ajax({
url: "test.html",
context: document.body
}).done(function() {
$(this).addClass("done");
}).fail(function() {
alert("error");
}).always(function() {
alert("complete");
});
需要注意的是,Ajax请求确实是异步的。不过,该请求是由浏览器新开一个线程发起的。当请求状态发生变更时,如果此前已设置回调函数,异步线程会将状态变更事件放入JavaScript引擎的处理队列中等待处理。详细信息可参考:http://www.phpv.net/html/1700.html
回调的执行时机与场景
回调函数的执行时机
在同步情境下,回调函数通常在最后执行;而在异步情境下,回调函数有可能不执行,原因可能是事件未被触发或条件未满足。
回调函数的典型使用场景
资源加载:动态加载JS文件后执行回调,加载iframe后执行回调,Ajax操作回调,图片加载完成执行回调等。 DOM事件及Node.js事件基于回调机制(Node.js中回调可能会出现多层嵌套问题)。 setTimeout的延迟时间设为0,这种技巧常被使用,此时setTimeout调用的函数实质上是回调的一种体现。 链式调用:在赋值器(setter)方法中(或本身无返回值的方法中)容易实现链式调用;而取值器(getter)较难实现,因为需要返回所需数据而非this指针。若需实现链式方法,可通过回调函数达成。 关于setTimeout、setInterval函数调用返回值的获取。由于这两个函数是异步的,其调用时序与程序主流程相对独立,因此无法在主体中等待其返回值。它们被触发时程序不会暂停等待,否则便失去了setTimeout及setInterval的意义。因此,使用return已无意义,只能采用callback。callback的意义在于将timer执行的结果通知给代理函数以便及时处理。
回调函数实战案例
网上收集了相关资料,经过理解后,自己整理出一个示例:
具体代码如下:
function fun(num,callback){
if(num<0) {
alert("调用A层函数处理!");
alert("数据不能为负,输入错误!");
}else if(num==0){
alert("调用A层函数处理!");
alert("该数据项不存在!");
}else{
alert("调用B层函数处理!");
callback(1);
}
}
function test(){
var num=document.getElementById("score").value;
fun(num,function(back){ //匿名B层处理函数
alert(":"+back);
if(num<2)
alert("数字为1");
else if(num<=3)
alert("数字为2或3!");
else
alert("数字大于3!");
})
}
当函数开始执行fun的时候,先跑去找判定num是否是负数或者为零,否则执行B层处理函数alert(":"+back);输出1,判定为<2、<=3、>3等情况。
经验小提示:
最好保证回调存在且必须是函数引用或者函数表达式: (callback && typeof(callback) === "function") && callback();
具体代码如下:
var obj={
init : function(callback){
//TODO ...
if(callback && typeof(callback) === "function") && callback(){
callback('init...');//回调
}
}
回调与轮询的对比
最后,关于为什么要使用回调函数呢?下面的比喻很生动有趣。
你有事去隔壁寝室找同学,发现人不在,你怎么办呢?
方法1,每隔几分钟再去趟隔壁寝室,看人在不 方法2,拜托与他同寝室的人,看到他回来时叫一下你
前者是轮询,后者是回调。
那你说,我直接在隔壁寝室等到同学回来可以吗?
可以啊,只不过这样原本你可以省下时间做其他事,现在必须浪费在等待上了。把原来的非阻塞的异步调用变成了阻塞的同步调用。
JavaScript的回调是在异步调用场景下使用的,使用回调性能好于轮询。
更简单一点:
“我现在出发,到了通知你” 这是一个异步的流程,“我出发”这个过程中(函数执行),“你”可以去做任何事,“到了”(函数执行完毕)“通知你”(回调)进行之后的流程