告诉你什么是javascript的回调函数


Posted in Javascript onSeptember 04, 2014

函数也是对象

想弄明白回调函数,首先的清楚地明白函数的规则。在javascript中,函数是比较奇怪的,但它确确实实是对象。确切地说,函数是用Function()构造函数创建的Function对象。Function对象包含一个字符串,字符串包含函数的javascript代码。假如你是从C语言或者java语言转过来的,这也许看起来很奇怪,代码怎么可能是字符串?但是对于javascript来说,这很平常。数据和代码之间的区别是很模糊的。

//可以这样创建函数
var fn = new Function("arg1", "arg2", "return arg1 * arg2;");
fn(2, 3); //6

这样做的一个好处,可以传递代码给其他函数,也可以传递正则变量或者对象(因为代码字面上只是对象而已)。

传递函数作为回调

很容易把一个函数作为参数传递。

function fn(arg1, arg2, callback){
 var num = Math.ceil(Math.random() * (arg1 - arg2) + arg2);
 callback(num);//传递结果
}

fn(10, 20, function(num){
 console.log("Callback called! Num: " + num); 
});

//结果为10和20之间的随机数

可能这样做看起比较麻烦,甚至有点愚蠢,为何不正常地返回结果?但是当遇上必须使用回调函数之时,你也许就不这样认为了!

别挡道

传统函数以参数形式输入数据,并且使用返回语句返回值。理论上,在函数结尾处有一个return返回语句,结构上就是:一个输入点和一个输出点。这比较容易理解,函数本质上就是输入和输出之间实现过程的映射。

但是,当函数的实现过程非常漫长,你是选择等待函数完成处理,还是使用回调函数进行异步处理呢?这种情况下,使用回调函数变得至关重要,例如:AJAX请求。若是使用回调函数进行处理,代码就可以继续进行其他任务,而无需空等。实际开发中,经常在javascript中使用异步调用,甚至在这里强烈推荐使用!

下面有个更加全面的使用AJAX加载XML文件的示例,并且使用了call()函数,在请求对象(requested object)上下文中调用回调函数。

function fn(url, callback){
 var httpRequest;
//创建XHR
 httpRequest = window.XMLHttpRequest ? new XMLHttpRequest() :
 //针对IE进行功能性检测


window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : undefined;
 
 httpRequest.onreadystatechange = function(){
  if(httpRequest.readystate === 4 && httpRequest.status === 200){
//状态判断
   callback.call(httpRequest.responseXML); 
  }
 };
 httpRequest.open("GET", url);
 httpRequest.send();
}

fn("text.xml", function(){

//调用函数
 console.log(this); 
//此语句后输出
});

console.log("this will run before the above callback.");
//此语句先输出

我们请求异步处理,意味着我们开始请求时,就告诉它们完成之时调用我们的函数。在实际情况中,onreadystatechange事件处理程序还得考虑请求失败的情况,这里我们是假设xml文件存在并且能被浏览器成功加载。这个例子中,异步函数分配给了onreadystatechange事件,因此不会立刻执行。

最终,第二个console.log语句先执行,因为回调函数直到请求完成才执行。

上述例子不太易于理解,那看看下面的示例:

function foo(){
 var a = 10;
 return function(){
  a *= 2;
  return a;  
 }; 
}
var f = foo();
f(); //return 20.
f(); //return 40.

函数在外部调用,依然可以访问变量a。这都是因为javascript中的作用域是词法性的。函数式运行在定义它们的作用域中(上述例子中的foo内部的作用域),而不是运行此函数的作用域中。只要f被定义在foo中,它就可以访问foo中定义的所有的变量,即便是foo的执行已经结束。因为它的作用域会被保存下来,但也只有返回的那个函数才可以访问这个保存下来的作用域。返回一个内嵌匿名函数是创建闭包最常用的手段。

回调是什么?

看维基的 Callback_(computer_programming) 条目:

In computer programming, a callback is a reference to a piece of executable code that is passed as an argument to other code.

jQuery文档How jQuery Works#Callback_and_Functio...条目:

A callback is a function that is passed as an argument to another function and is executed after its parent function has completed. The special thing about a callback is that functions that appear after the "parent" can execute before the callback executes. Another important thing to know is how to properly pass the callback. This is where I have often forgotten the proper syntax.

百科:回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

因此,回调本质上是一种设计模式,并且jQuery(包括其他框架)的设计原则遵循了这个模式。

在JavaScript中,回调函数具体的定义为:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

因此callback 不一定用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。

例子
一个同步(阻塞)中使用回调的例子,目的是在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引擎的处理队列中等待处理。*/

回调什么时候执行

回调函数,一般在同步情境下是最后执行的,而在异步情境下有可能不执行,因为事件没有被触发或者条件不满足。

回调函数的使用场合

资源加载:动态加载js文件后执行回调,加载iframe后执行回调,ajax操作回调,图片加载完成执行回调,AJAX等等。
DOM事件及Node.js事件基于回调机制(Node.js回调可能会出现多层回调嵌套的问题)。

setTimeout的延迟时间为0,这个hack经常被用到,settimeout调用的函数其实就是一个callback的体现

链式调用:链式调用的时候,在赋值器(setter)方法中(或者本身没有返回值的方法中)很容易实现链式调用,而取值器(getter)相对来说不好实现链式调用,因为你需要取值器返回你需要的数据而不是this指针,如果要实现链式方法,可以用回调函数来实现setTimeout、setInterval的函数调用得到其返回值。由于两个函数都是异步的,即:他们的调用时序和程序的主流程是相对独立的,所以没有办法在主体里面等待它们的返回值,它们被打开的时候程序也不会停下来等待,否则也就失去了setTimeout及setInterval的意义了,所以用return已经没有意义,只能使用callback。callback的意义在于将timer执行的结果通知给代理函数进行及时处理。

回调函数的传递

上面说了,要将函数引用或者函数表达式作为参数传递。

$.get('myhtmlpage.html', myCallBack);//这是对的
$.get('myhtmlpage.html', myCallBack('foo', 'bar'));//这是错的,那么要带参数呢?
$.get('myhtmlpage.html', function(){//带参数的使用函数表达式
myCallBack('foo', 'bar');
});

另外,最好保证回调存在且必须是函数引用或者函数表达式:
(callback && typeof(callback) === "function") && callback();

Javascript 相关文章推荐
Ext.FormPanel 提交和 Ext.Ajax.request 异步提交函数的区别
Nov 12 Javascript
jQuery学习笔记之控制页面实现代码
Feb 27 Javascript
JS简单实现文件上传实例代码(无需插件)
Nov 15 Javascript
用html5 js实现点击一个按钮达到浏览器全屏效果
May 28 Javascript
JS封装cookie操作函数实例(设置、读取、删除)
Nov 17 Javascript
jQuery选择器及jquery案例详解(必看)
May 20 Javascript
JS小数运算出现多为小数问题的解决方法
Jun 02 Javascript
BootStrap 附加导航组件
Jul 22 Javascript
微信小程序 高德地图SDK详解及简单实例(源码下载)
Jan 11 Javascript
DataTables添加额外的查询参数和删除columns等无用参数实例
Jul 04 Javascript
vue组件中watch props根据v-if动态判断并挂载DOM的问题
May 12 Javascript
vue使用wavesurfer.js解决音频可视化播放问题
Apr 04 Vue.js
jquery学习总结(超级详细)
Sep 04 #Javascript
JavaScript实现按Ctrl键打开新页面
Sep 04 #Javascript
js实现图片在未加载完成前显示加载中字样
Sep 03 #Javascript
jquery.idTabs 选项卡使用示例代码
Sep 03 #Javascript
jQuery 1.9移除了$.browser可以使用$.support来替代
Sep 03 #Javascript
JavaScript用JQuery呼叫Server端方法示例代码
Sep 03 #Javascript
禁止iframe页面的所有js脚本如alert及弹出窗口等
Sep 03 #Javascript
You might like
PHP 八种基本的数据类型小结
2011/06/01 PHP
PHP 7的一些引人注目的新特性简单介绍
2015/11/08 PHP
PHP中empty和isset对于参数结构的判断及empty()和isset()的区别
2015/11/15 PHP
php使用正则验证中文
2016/04/06 PHP
javascript 框架小结 个人工作经验
2009/06/13 Javascript
jquery创建一个新的节点对象(自定义结构/内容)的好方法
2013/01/21 Javascript
javascript批量修改文件编码格式的方法
2015/01/27 Javascript
使用纯javascript实现经典扫雷游戏
2015/04/23 Javascript
JavaScript实现横向滑出的多级菜单效果
2015/10/09 Javascript
Bootstrap实现带动画过渡的弹出框
2016/08/09 Javascript
form表单序列化详解(推荐)
2017/08/15 Javascript
基于JavaScript实现前端数据多条件筛选功能
2020/08/19 Javascript
Vue-Router实现组件间跳转的三种方法
2017/11/07 Javascript
实现div内部滚动条滚动到底部和顶部的代码
2017/11/15 Javascript
Vue 应用中结合vux使用微信 jssdk的方法
2018/08/28 Javascript
JS实现倒计时图文效果
2018/11/17 Javascript
详解关于React-Router4.0跳转不置顶解决方案
2019/05/10 Javascript
vue实现分环境打包步骤(给不同的环境配置相对应的打包命令)
2019/06/04 Javascript
微信小程序学习总结(二)样式、属性、模板操作分析
2020/06/04 Javascript
利用React高阶组件实现一个面包屑导航的示例
2020/08/23 Javascript
[10:28]2018DOTA2国际邀请赛寻真——VGJ.S寻梦之路
2018/08/15 DOTA
[20:30]职业巡回赛回顾
2018/08/09 DOTA
python实现查询IP地址所在地
2015/03/29 Python
老生常谈Python startswith()函数与endswith函数
2017/09/08 Python
Python 控制终端输出文字的实例
2019/07/12 Python
Python 类方法和实例方法(@classmethod),静态方法(@staticmethod)原理与用法分析
2019/09/20 Python
python爬虫爬取图片的简单代码
2021/01/18 Python
韩国休闲女装品牌网站:ANAIS
2016/08/24 全球购物
法国创作个性化T恤衫和其他定制产品平台:Tostadora
2018/04/08 全球购物
EJB timer的种类
2014/10/28 面试题
中学生检讨书1000字
2014/10/28 职场文书
贫困生助学金感谢信
2015/01/21 职场文书
冰雪公主观后感
2015/06/16 职场文书
八年级作文之感悟亲情
2019/11/20 职场文书
读《儒林外史》有感:少一些功利,多一些真诚
2020/01/19 职场文书
python读取并查看npz/npy文件数据以及数据显示方法
2022/04/14 Python