前言

  • 因为之前一直不理解javascript里面回调函数的含义,直到后面用的多了,才慢慢理解怎么使用回调函数!所以今天想把自己对回调函数的理解记录下来加深印象!这篇文档尽量把相关概念说得尽可能简单,所以认认真真看完我相信对怎样理解回调函数还是有一定帮助的!当然了有说的不对的地方,也希望提出意见,让我们一起共同成长!

什么是回调函数

  • 首先我们把“回调函数”分为两个部分来理解:“回调”和“函数”!作了这样的划分之后,我们可以首先得到一个印象————回调函数本质上是一个普通的javascript函数而已,其实javascript中很多概念它们都是函数另外一种称呼而已,比如闭包什么的!关键看你怎么理解了!再从“回调”上理解,“回调”说明回调函数调用是被动的,颇有点“回头再用”的意思,怎么理解呢!就是一个函数我们定义了之后,我们并不是单独调用它 ,而是把它放到某个“大的函数”里面,等到“大函数”一些“状态”满足之后再来调用回调函数,也就是说回调函数是等着别人来调用它的,它本身是不直接调用自己的!从某种意义上来说,javascript里面所有函数都是回调函数,因为你可以这样理解———所有的函数都是等js文件准备好之后,再调用,它们是“等待着被动调用的”!来看个例子吧:
  1. function a(){
  2. console.log('a');
  3. };
  4. function b(){
  5. console.log('b');
  6. a();
  7. };
  8. b();//输出b , a

这里a就是一个回调函数,因为它是等待着b函数调用之后在b函数里面调用的!

常见的回调函数形式

  • 在介绍回调函数的常用形式之前,我们先来说一下javascript里面的函数————首先js函数是一个很强大的概念,得益于函数在javascript中是第一等对象,函数既可以作对象的方法,也可以当作参数传递给其他函数,更可以作为返回值在函数里面返回等等。所以等等,函数可以出现的地方,都有可能是回调函数出现的形式,大致上分为三种吧:

_ 1、作为响应事件的处理函数,如下:

  1. document.body.onclick = function(){
  2. alert("this is body");
  3. };

_ 2、作为参数传递给其它函数,如下:

  1. function inputTwoNumber(x,y,callback){
  2. if(Object.prototype.toString.call(x) !== '[object Number]' || Object.prototype.toString.call(y) !== '[object Number]'){
  3. return;
  4. }else{
  5. return callback(x,y);
  6. };
  7. };
  8. inputTwoNumber(3,5,function(num1,num2){
  9. return num1+num2;
  10. });//return 8

上面,我们在inputTwoNumber的第三个参数上传递了一个匿名函数实现回调,其实我们我们也可以按下面的方式,性质是一样的:

  1. function add(num1,num2){
  2. return num1+num2;
  3. };
  4. inputTwoNumber(3,5,add);

采用匿名函数的方式之所以能实现是因为js中函数是一等对象,可以传递传递给其他函数作参数!

_ 3、作为普通方法在其它函数里面调用,如:

  1. function a (){
  2. console.log('a');
  3. };
  4. function b(){
  5. console.log('b');
  6. a();
  7. };

当然了可能还有其他形式,就不一一列举了,这里没有列出来的,可能我一下子没有想到,但是只要能理解就好了!

 回调函数有什么好处

  • 1、把一个大函数分解成一些小函数,提高代码复用,比如上面的inputTwoNumber函数,即使不用回调函数它也可以实现相加的操作,但是把所有逻辑都写在一个函数里面是很不好了,将来函数越来越到,将变得难以维护。而且我们使用回调函数之后,可以把回调函数用在其它实现相加运算的地方,这样代码复用率也提高起来了!

  • 2、代码更灵活了,还是拿上面inputTwoNumber函数来说,如果不用回调函数,直接把加法逻辑写在函数体里面,我们能想到的问题就是,你这个函数最后只能实现加法运算了,并不能做其他事情了,但是我们通过回调函数就可以实现inputTwoNumber只是一个传递数值的“身体”而已,而我们具体做什么我们可以自由发挥,这样是不是很舒服呢!

  • 3、实现异步调用,因为我们有些操作并不是同步的,所有我们需要编写回调函数,用于将来在某个事件触发的时候再来调用,比如,事件响应函数、所有异步操作之后调用的函数!如下:

  1. document.body.onclick = function(){
  2. alert("我等点击触发的时候才调用!");
  3. };

用回调函数实现一个异步向后台实现读出json数据的例子

  • 我们来看一下如下的一段代码:
  1. var xhr = new XMLHttpRequest();
  2. xhr.open('GET','./test.json',true);
  3. xhr.send();
  4. xhr.onreadystatechange = function(){
  5. if(xhr.readyState == 4 && xhr.status == 200){
  6. setDataToUrl(xhr.responseText);
  7. };
  8. };
  9. function setDataToUrl(data){
  10. data = JSON.parse(data);
  11. var dom = document.getElementById('test');
  12. var oul = document.createElement('ul');
  13. for(var pro in data){
  14. var oli = document.createElement('li');
  15. oli.innerHTML = data[pro];
  16. oul.appendChild(oli);
  17. };
  18. dom.appendChild(oul);
  19. };

后台的json文件如下:

  1. /*test.json*/
  2. {
  3. "name":"myCustomJson",
  4. "description":"this is a test json file"
  5. }

结果截图如下:

06. javascript回调函数callback以及理解Promise - 图1

回调函数是异步的吗?

  • 曾经这个问题困扰我很久,因为我一直理解回调函数就是一个函数而已,为什么很多人是它是异步的呢!所以我不认为回调函数不是异步的,最后通过不断练习才证明了我的观点是正确的————回调函数本身不是异步的,只是引起回调函数调用的这个行为本身是异步的而已,还是看下面的代码:
  1. document.body.onclick = function(){
  2. console.log("回调函数");
  3. };

就像上面的事件处理函数它就是一个普通的匿名函数,它本身不是异步的,我们之所以说他是异步的,是因为本身点击这个事件是异步的,而通常我们所说的异步函数函数又用在这样的场景中,所以我们久而久之就习惯把回调函数说成异步的了,这点要切记,但是回调函数本身来说不是异步的,只是引起它调用的那个操作是异步的

 回调函数不好的地方

  • 回调函数一个最大的影响就是,就是容易形成回调地狱,设想这样的场景:a函数回调b,b函数回调c,c函数回调d…如此下去,是不是很绕呢!你的代码将变得晦涩难懂,而你也很难管理。这样的场景在向后台获取数据的时候经常遇到,看下面的示例代码:
  1. $('xx').animate({xxxx}, function(){
  2. $('xx').animate({xx},function(){
  3. //do something,后面可能还有回调
  4. },1000)
  5. },1000)

解决回调函数地狱的一些方法

  • 在es6中实现了几种方式来解决回调函数所带来的回调地狱问题如:Generator函数、THUNK函数、async函数及Promise等。有机会大家可以去了解下,这里只简单介绍一下Promise方式,其它方式不介绍。

  • Promise英文译为“承诺”,意思在将来某个时间会给出答复,所以这里用这个意思来理解是非常合适的,Promise把异步操作化作三个状态:Pending(进行中)、Resolved(已成功)及 Rejected(以失败)。这三个状态之间只有两种转换方式:pending -> Resolved和 Pending -> Rejected。再无没有其它状态转换方式。对于Promise的细节这里不做介绍,因为相关文档肯定比这里介绍的仔细哦!

  • Promise的原理:Promise接受两个参数,一个用于处理异步成功之后的操作,一个用于失败之后的操作。这两个参数所需要的参数都是异步操作返回的数据!我们用Promise实现上面的从后台得到json数据:

  1. function getJson(){
  2. var xhr = new XMLHttpRequest();
  3. xhr.open('GET','./test.json',true);
  4. xhr.send();
  5. return new Promise(function(resolve,reject){
  6. xhr.onreadystatechange = function(){
  7. if(xhr.readyState == 4 && xhr.status == 200){
  8. resolve(xhr.responseText);//成功之后返回的数据放在Promise第一个参数里面
  9. }else if(xhr.status>=300 || xhr.status <200){
  10. reject('');//请求失败之后,在第二个参数实现出错提示
  11. };
  12. };
  13. });
  14. };
  15. var printDiv = getJson();
  16. printDiv.then(function(data){
  17. data = JSON.parse(data);
  18. var dom = document.getElementById('test');
  19. var oul = document.createElement('ul');
  20. for(var pro in data){
  21. var oli = document.createElement('li');
  22. oli.innerHTML = data[pro];
  23. oul.appendChild(oli);
  24. };
  25. dom.appendChild(oul);
  26. });

后续内容正在补充中…….