Java与JavaScript的回调函数类比

本月中旬开的题,现在终于把坑添好了,Orz。
Java与JavaScript(下文简称JS)除了名称上的相似之外,其它方面也并非风马牛不相及,因为同属OO家族,有些特性还是可以进行相互类比来帮助理解的:例如下面我们所要讨论的回调(Callback):

所谓回调,全称回调函数(callback function)。回,转也。是指函数A的功能片段传递给函数B(注册的过程),当B触发了某条件后,去调用该功能片段的指向(实现),这时A即称为回调函数。如果用Java的方式来叙述的话,就是类A中封装了一个接口InterfaceC的方法,类B按InterfaceC的约定实现该方法后再把自己传回给A,由A进行调用。因为Java没有函数的概念,这时就不是“函数回调”,而是“类回调”了,但是思想还是一样的。网上看到句挺好的解释,回调就是:

If you call me, I will call back.

这样做的好处是什么呢?最显著的一点即是分离方法与具体实现,让调用者与被调者解耦合。

一、先来看看JS中的回调应用

把回调的思想应用于JS,可以实现功能与内容的分离。一般来说,对于一个完善的网页页面,代码有三部分组成:内容(HTML代码)、外观(CSS代码)以及功能(JS代码)。而分离功能与内容,可以让我们的代码更容易建立与维护。杂糅总是抓狂的代名词。

JS中把函数看成变量,即把函数主体看成值,而函数名称则为变量名称。

普通的,JS中声明一个函数

function callBack(){
    alert(“I'm a function”);
}

再来看另一种函数声明方式:

var callBack = function(){
    alert(“I'm a function”);
}

上述这种方式如果舍去了函数变量名,只有一个function函数体的状态称为函数字面量(function literal)或匿名函数(anonymous function)。

因为变量直接可以保存函数的引用也就意味着对于函数,我们能像操纵变量一样去操纵它。

最简单而且是最常见的例子:

以前我们喜欢把JS代码指派给HTML的属性:<body onload=”callBack()”>

现在,更优雅的方式是在<script type=”text/javascript”><script>的JS代码内部中,使用函数引用设定回调函数,即window.onload = callBack

此处调用函数在于网页载入完成后触发,相当于在浏览器调用window.onload()函数时,实际上调用的是callBack()函数,因为我们把后者的引用覆盖了前者属性(变量)中原先存放的引用。

但是现在有一个问题:假如我们现在的callBack()函数有参数传入该怎么办呢?因为变量名中一旦出现了“(args)“即意味着函数调用。

很简单,这时就需要我们刚刚提到的函数字面量了:

window.onload = function(args){
    alert(args);
}

此时,匿名函数一声明完,它的引用未通过中间变量的保存,直接覆盖了原函数变量中保存的引用。而对于其它事件的指派,只需要在onload中指派的匿名函数体中指派就可以了——让onload事件处理函数成为事件初始化函数。

关于回调,Ajax处理服务器响应的数据时也有其中的应用。

二、下面看看Java的回调方式:

2.1定义一个回调接口

package callbackdemo.ijava.me;

/**
* 回调函数接口
* @author doslin
*/
public interface CallBack {
    public void execute();
}

2.2答题者回答完立即通知提问者

package callbackdemo.ijava.me;

/**
* 问题解决者
* @author DosLin
*/
public class Solver {
    private CallBack cb = null;//回调引用

    public void getQuestion(CallBack asker){
        cb = asker;
    }

    public void soloveIt(){
        System.out.println("I'm Solver,the problem is solved.");
    cb.execute();
    }

}

2.3 提问者实现了回调接口,意味着有回调方法

package callbackdemo.ijava.me;
/**
* 提问者
* @author DosLin
*/
public class Asker implements CallBack{
    public Asker(){
        System.out.println("I'm Asker,I hava a question..");
    }

    // 问题解决后响应方式
    @Override
    public void execute() {
        System.out.println("Thank you..");
    }

    public static void main(String[] args){ //测试
        Asker asker = new Asker();//提问者
        Solver solver = new Solver();//解决者

        solver.getQuestion(asker);//看到提问者发问
        solver.soloveIt();//通知提问者问题被解决
    }
}

综上,回调函数的重要性不言而喻,它可以取代代码中的直接调用函数,使之成为创建回调函数,当且仅当特定事件触发后才会启动回调函数。