回调
解释这个概念之前,需要分清楚调用与传入的区别。
调用与传入
调用指的是直接执行这个函数,而传入指的是将这个函数当作一种变量来传递,并没有执行这个函数。
回调
回调(callback)指的是程序在执行的时候,调用了一个以前传入的函数,这个函数称之为回调函数。
回调的意思实际上是,在发送方向接收方传递一个函数时,发送方不执行这个函数,而委托接收方等到合适的时机执行。而接收方在接收到这个函数时,也不会马上执行这个传入函数,而是选择一个合适的时机执行。
回调函数是从传入这个函数的发送方的视角来说的,而不是从接收方的视角来说的。因为调用函数是一件很普通的事情,光是调用不能称之为回调。这个函数必须是另一方事先传入才能称为回调。而光从调用这个角度来说,接收方也不能看出这个函数是不是回调函数,所以回调函数不能从接收方的视角来看。而调用回调这个过程通常对发送方是透明的,发送方只传不调。因此,回调是从发送方的角度来看待的。
【Q & A】
-
Q:为什么调用普通的函数不称为回调呢?
A:因为对于普通的函数,使用过程很简单,就只是调用方调用这个函数而已。而回调函数有一个传入和委托的过程。回调函数的发送方并不负责该函数的调用,只是负责该函数的实现和传入。回调函数的调用比普通函数的调用要复杂得多,编程风格也有所不同。
-
Q:回调中的"回"指的是什么意思?
A:回调(callback)直译来说就是“回过去调用”,也就是“改变当前的程序走向,调用以前的那个函数”。所以“回”是“反”的意思,指的就是(回调接收方的)程序走向突然倒过来,向以前的方向执行。
关于这个名词,国外也有不少批评。有人认为应该将名称改为“callafter”,也就是“后调”。"后调"一词比"回调"要更加表意,"回调"看起来更深奥一些。一个专有名词当然是能起得望文生义最好。不过笔者还是认为“回调”一词比"后调"要更好。"后调"会让人误认为后调函数是由发送方传入并在以后调用。而“回调”才能体现出回调函数的执行流程与普通函数不同,甚至相反。
回调函数
回调函数一般是经过事先设定(传入),等到某事件发生时就被触发的函数。比方说,事件监听器中设定的函数等等。
有些时候,如果回调函数执行时间很短,有返回值且能控制程序的走向,那么也把它称之为 钩子
(hook)。
在有些编程语言中,如果允许回调函数使用传入处的局部变量,那么也把它称之为 闭包
(closure)。
回调函数与普通函数的区别
回调函数与普通函数的区别在于,一般的函数是自己负责编写实现,自己负责调用。而回调函数是自己负责编写实现,但自己不负责调用。自己只是把函数具体实现编写好,然后交给别人来调用。
另一个区别是,(为了方便,这里将调用回调函数或普通函数的函数称之为主函数)。主函数在调用普通函数时,数据的传输流程一般为,先从普通函数调用时的实参传入,然后到达普通函数的形参,最后由普通函数的返回值到达主函数。对不同的编程语言,有些时候,普通函数也可以通过传入的实参、抛出的异常来将结果反馈给主函数。
但主函数使用回调函数时,这个过程描述起来就比较复杂了。回调函数通常是异步调用的,因为在主函数传入回调函数时,并没有马上执行该函数,而是改为异步执行,因此回调函数向主函数反馈结果时也一般不能像普通函数一样通过返回值来进行同步阻塞式反馈。通常,这同时还使用了一种称之为 闭包
的技术,来使得回调函数在真正执行时,可以修改主函数的数据,来达到来主函数反馈数据的目的。
【附】
-
什么是闭包:https://blog.csdn.net/wangpaiblog/article/details/113360870
-
什么是同步与异步、并行与并发、阻塞与挂起:https://blog.csdn.net/wangpaiblog/article/details/116114098
-
什么是同步阻塞、同步非阻塞、异步阻塞、异步非阻塞:https://blog.csdn.net/wangpaiblog/article/details/117236684
钩子
钩子(hook)指的是一个程序预设的子程序跳转入口。在不同的编程语言、不同场景下,钩子可以指预先约定的某一类函数名、某个类的接口引用字段等等。
一般来说,一个框架编写完成,就会形成一种黑盒。但如果想让这个黑盒在不同的需求下有不同的行为,就需要将一些程序接口暴露给用户,这就是钩子。钩子可以让用户在不需要深入了解内部原理的情况下使用这个框架,并能控制这个框架的程序走向。
钩子是 IoC
思想的体现。它们有时候也称之为一种程序里的后门。它们体积不大,运行时间不长,但却能控制程序的走向。它是开发者提供给用户的一个功能强大的工具。
在有些场景中,钩子是一种回调函数(callback)。
广义的钩子
广义的钩子指的是一个可以控制程序流程的一段代码。但这段代码与程序其它部分的代码属于低耦合,即可以通过不改动其它部分的代码的条件下,任意地更改这段代码来控制程序的走向。
因此,广义的钩子不仅包括上面介绍的钩子,还包括一些类中预设的布尔函数,比方说,可以对某一系列的类中都设置一个布尔函数,然后用该布尔函数的返回值来决定程序走向,则该布尔函数就是一个广义的钩子。
钩子与代理的区别
钩子和代理设计流程正好相反。
对于代理,一般来说,被代理的类是很早以前就已经设计好了,而代理类的设计时间晚于被代理类。代理类低耦合依赖于被代理类,但被代理类不依赖代理类。
对于钩子,含钩子的类是很早以前就已经设计好了,而钩子的具体实现而晚于含钩子的类。它们之间是一种低耦合地互相依赖的关系。
钩子体现了整体与局部、动静分离的思想。钩子只是一个程序中局部未实现的代码。缺少钩子,程序无法运行。
代理则体现了一种包装的思想。代理类只是对被代理类的一种升级。它们对外的接口相同,代理的使用方无法分辨代理类与被代理类。
关于代理、委托,可见笔者的另一篇博客:
代理、委托、打桩的区别:
https://blog.csdn.net/wangpaiblog/article/details/130691093
钩子与委托的区别
钩子可以看作是委托的升级版或者是特别版,其耦合度比一般的委托要低。
在下面的这些情况下,应当设计成钩子而不是委托:
-
程序已经抽象出来一套生命周期
-
活动的实现与使用之间没有高内聚的要求
-
需要将接口对第三方暴露
-
对外暴露的接口的返回值将决定程序的走向
钩子与回调函数的区别
钩子与回调函数这两个词的意思非常接近,只是描述事物的侧重点有些不同,就像等边三角形与等角三角形,因此经常混用。
钩子更像是一种程序运行必需的常规配置。而回调函数更像是一种陷阱,在程序发生意外时才会触发。
钩子相对于普通的回调函数,通常有如下特点:
-
内容短小、操作步骤很基本、耗时小
-
可以通过其返回值来控制程序走向
-
一般不会开启新线程或后台任务
回调函数相对于钩子,通常有如下特点:
-
回调函数由事件触发,且允许反复触发
-
对于 UI 领域,这种函数往往是
纯函数
,且返回值常常为空(void) -
当程序的运行轨迹改变时可以触发它,但它不会主动控制程序走向
关于什么是纯函数,可见笔者的另一篇博客:
什么是纯函数?:
https://blog.csdn.net/wangpaiblog/article/details/114005888
句柄
句柄(handle)指的是一种通过它能够访问程序内部,甚至控制程序的某种数值。在不同的编程语言、不同场景下,它可以指的是一种内存地址、指针、类对象等等。
【注意】
句柄的英文是 handle
,而不是 handler
。但 handler
也是一个编程中的术语,它和 handle
完全不是同一个东西。但由于英文语法与中文语法的区别,很难找到一个没有用过的两个字的中文词汇来翻译它。handler
实际上是一种事件处理程序(处理器),英文解释是 callback routine
,因此,它的意思更接近回调函数(callback)或者钩子(hook)。所以,handler
并不是句柄的意思。
句柄与钩子的区别
钩子是开发者提供给用户的一个功能强大的工具。就功能强大而言,这有点类似于句柄。但从使用方法来说,它们更像是相反的东西。
句柄提供了一些可供调用的 API,而钩子则要求自己实现这些 API 的内容,然后注入到程序中。