回调函数是很多程序员曾有疑问的一个概念,最近又回顾了下这个概念,看到大概是条费鱼和no.body的回答又有新的收获,自己也重新整理一下。
1️⃣概念
回调函数也是一个函数,形式上和一般函数相同。
比如,下面给出一个求和函数sum(),这里是没办法区分这个函数是一般函数还是回调函数。
def sum(a, b):
return a + b
回调函数与一般函数的区别是,回调函数是被当做一个参数传递给其他函数,供其调用;而一般函数是在需要的地方直接调用。
比如,下面添加了一个计算函数calculate(),将求和函数sum()作为参数operate传入,这时,求和函数sum()就是一个回调函数了。
def sum(a, b):
return a + b
def calculate(a, b, operate):
return operate(a, b)
def main():
i = calculate(2, 3, sum)
print(i)
if __name__ == "__main__":
main()
其实,上面回调过程有三个部分:
起始函数:确定使用的回调函数。对应上面的main函数。
库函数(中间函数):需要传入回调函数参数的函数。对应上面的calculate函数。
回调函数:被当做一个参数的函数。对应上面的sum函数。
下图是他们的关系(图片来源:维基百科):
2️⃣用途
那为什么要有回调函数呢,为什么不用一般函数?
回调函数可以将调用权限转移给其它函数或者系统。
这样其它函数或者系统就可以根据实际情况进行调用,比如延时调用。
就好像你去商店买东西,但是没货了,你将电话留给老板,等有货了老板再联系(callback)你。
做框架代码或者底层代码会经常遇到这样的情况,具体的逻辑需要上层应用提供,这时就需要给上层暴露接口或提供回调函数参数。
其实,接口和回调函数有一定的相通性。
在Java最开始中没有回调函数这个概念,相应的功能都是通过接口实现的。到Java8函数式编程之后才有这个概念。
接口是多方法回调的正式契约版本,回调函数是单方法接口的轻量版本。
回调函数多用于以下场景:
事件处理
回调函数可用于事件处理。向系统特定类型的事件注册回调函数,当事件发生时系统调用对应函数。
比如订单页面上的下单按钮,用户点击后执行处理下单业务的回调函数。
异步操作
回调函数可用于实现异步处理。异步执行某个操作,并提供一个在操作完成时被调用的回调函数。
比如商品页面向后台发起数据请求,等请求成功后执行渲染数据的回调函数。
多态性
通过提供不同实现的回调函数,可以实现调用函数的多态性。
之前买东西的比喻,你还可以将地址留个老板,等有货了老板送货上门。
同理,下面在添加了新的回调函数multiply(),向计算函数calculate()注入不同的回调函数,就可以实现不同的功能。
def sum(a, b):
return a + b
def multiply(a, b):
return a * b
def calculate(a, b, operate):
return operate(a, b)
def main():
i = calculate(2, 3, sum)
print(i)
i = calculate(2, 3, multiply)
print(i)
if __name__ == "__main__":
main()
3️⃣阻塞(同步)回调和非阻塞(异步)回调
还有两个概念,阻塞(同步)回调和非阻塞(异步)回调。
其实,这两个也很好分别:
如果起始函数和回调函数在同一线程就是阻塞(同步)回调;
如果起始函数和回调函数不在同一线程就是非阻塞(异步)回调。