最近自己想学习下微信小程序,学习了几个小案例之后,里面的javascript代码已经彻底把我整晕了,它不像C语言一下,定义函数、使用函数、函数返回值。javascript里面有那么多异步啦回调啦,函数简直没有个函数的样子,之前虽然也接触过JavaScript,但也仅限于在做网页交互的时候,照葫芦画瓢的用一下,现在才发现对JavaScript中好多核心的概念不了解,我最想了解的概念是:回调函数,于是开始各种百度看文章看文档,总结整理此文。
在说回调函数之前,先简单说一下JavaScript另一特性:非阻塞IO,之前只是听说过这个词,举个例子感受一下(例子来源大佬写的一篇文章:nodejs setTimeout函数使用,在此引用并致谢):
//非阻塞IO,用setTimeout模拟文件IO等耗时操作
//定义函数abc
function abc(){
setTimeout(function(){console.log('3')},3000); //暂停3秒后,输出:3
console.log('1'); //输出:1
setTimeout(function(){console.log('2')},1000); //暂停1秒后,输出:2
}
//调用函数abc
abc();
//如果按顺序执行,执行完一行再执行下一行
//执行的结果应该是:3,1,2运行时间约4秒
//node.js采用非阻塞IO,程序按顺序执行,但并不等待当前代码执行完毕,即非阻塞IO。
//实际执行结果是:1,2,3,运行时间约3秒
//(这种方式执行代码给我的冲击还是非常大的,竟然还可以这样???c语言老师从来没讲过啊。。。)
//这就是非阻塞IO所带来的好处,永远不会产生死锁,因为它本身没有锁机制。
//同时,非阻塞IO也会带来一些问题:
//过程式编程中,有很多情况下是本句代码要求先前的代码执行完毕,
//如要调用之前处理的数据结果、和数据库交互等。
//nodejs中可以采用回调方式解决这个问题。
怎么通过回调来解决这个问题呢?我们把上面的例子修改一下,以便更清楚的描述我们遇到的问题:JavaScript的异步带来了高效,现在假如第一行的代码是耗时的文件读取(我们仍然用setTimeout方法模拟耗时操作),第二行的代码需要引用所读取到的文件,展示文件的内容,会是怎么的结果呢?看下面的例子:
//定义函数abc
function abc(){
var res=""; //存放读取的文件内容
//3秒后,文件读取完成,将内容放入到之前定义的变量res中
setTimeout(function(){res="i am file text";},3000);
//展示文件内容
console.log('the file is:'+res);
}
//执行函数abc
abc();
这时的执行结果是:the file is :null
这显然不符合我们得初心啊,刚才说了,可以使用回调函数解决这个问题,那么,先来看一下回调函数的定义,JS Api 里这样解释:
A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
大致意思是,回调函数作为参数传递到另外一个主函数中,待主函数执行完成后,执行回调函数。
具体到上面的例子,主函数应该是负责读取文件内容,回调函数负责处理读取到的文件内容,此处即展示文件内容。先来定义一个主函数:
//定义一个主函数main,负责读取文件内容:
function main(){
var res=""; //存放读取的文件内容
//3秒后,文件读取完成,将内容放入到之前定义的变量res中
setTimeout(function(){res="i am file text";},3000);
}
//定义一个回调函数callback,负责展示文件内容:
function callback(res){
console.log("the file is:"+res);
}
下面的问题如何将主函数与回调函数联系起来呢?按照定义,将回调函数作为参数传递给主函数,所以需要改造一下主函数,给主函数添加一个参数,并在读取文件完成后,将读取的文件内容传递给回调函数(粗体+斜体标出部分):
//重新定义主函数
function main(someFunction){
var res=""; //存放读取的文件内容
//3秒后,文件读取完成,将内容放入到之前定义的变量res中
setTimeout(function(){res="i am file text"; someFunction(res);},3000);
}
//执行定义的主函数和回调函数:
main(callback);
//执行的结果:
the file is: i am file text
至此,我们已经成功的利用回调函数解决了js非阻塞特性中,特定步骤完成后再执行其他代码的需求。
另外,在实际的应用中,回调函数往往不是提前定义好的,而是在执行主函数时定义的匿名函数处理主函数返回的结果,如上例,只定义主函数,不定义回调函数,执行主函数时再定义匿名的回调函数(粗体+斜体的部分为匿名回调函数):
//回调函数为匿名函数
main(function(res){
console.log(“the file is :”+res);
});
或者,通过箭头函数的形式,在执行主函数时定义回调函数(粗体+斜体的部分为匿名回调函数):
//回调函数为箭头函数
main(res=>{
console.log(“the file is : ”+res);
});
关于匿名函数和箭头函数,这里不再展开,请自行查看文档。以上,是我对JavaScript回调函数的理解,希望对你有帮助。