什么是回调函数?
我的理解,就是采用回调的方式调用函数。那什么又是回调的方式?
举个例子,老师L让学生A做一件事情,就是将全班同学的试卷给学生A,让他找出其中不及格的试卷。老师L接着又找到学生B,将这部分试卷交给他,让学生B把这些同学的名字记下来。看这倒霉老师安排这事儿,一看就不懂回调,首先学生A把全班同学的试卷都翻了个遍,学生
B又将其中不及格部分同学的试卷翻个遍,实际上是多余的for循环了。如果数据量很大(全班300万学生,不及格的140万),那势必会造成多余的时间人力浪费。那怎么用回调的方式呢?学生A说了:老师你给我一个会记名字的学生B,我找到一个不及格的就把试卷给他,他记下试卷上的名字就可以了。这样的话全班同学的试卷就只被找了一遍就完成任务了。那么通过以上的例子,我们就可以把学生B看成是学生A的回调函数。学生B可以看作是老师调用学生A找不及格试卷的方法的时候传给学生A的一个参数,在学生A需要的时候就调用这个参数做事情就可以了。这就可以说是回调的方式。

看看用Javascript表示的回调,首先不用回调的方式:

//定义函数, 创建html节点集合并返回
    var createNodes = function(sum)
    {
        var nodes = [], //定义节点数组
        node, //节点
        textNode,
        i = sum;

        while(i)
        {
            node = document.createElement("div");
            textNode = document.createTextNode
                           ("non-callback-function " + i);
            node.appendChild(textNode);
            nodes.push(node);

            i -= 1;
        }

        return nodes;
    };  

    //定义函数,将树节点集合逐一添加到文档某节点中
    var addNodes = function(child_Nodes, parent_Node)
    {
        var i=0, max = child_Nodes.length;

        for(; i<max; i+=1)
        {
            parent_Node.appendChild(child_Nodes[i]);
        }
    }; 

    var parent_Node = document.getElementsByTagName("body")[0];
    var child_Nodes = createNodes(10); //循环创建节点集合
    addNodes(child_Nodes, parent_Node); //循环节点集合添加到父节点



很明显的缺点,节点集合被遍历两次,如果遍历节点集合是一个消耗很大的操作,会造成资


源浪费。



当然我们也可以将父节点传给方法createNodes,如:


var createNodes = function(sum, parent_Node)
     {
         var node, //节点
         textNode,
         i = sum;

         while(i)
         {
             node = document.createElement("div");
             textNode = document.createTextNode
               ("non-callback-function " + i);
             node.appendChild(textNode);

             parent_Node.appendChild(node); //这个地方将节点加入父节点

             i -= 1;
         }

     };

     var parent_Node = document.getElementsByTagName("body")[0];
     createNodes(100, parent_Node);



这种方式看起来确实更加的简单了。但是我们可以发现,此时的createNodes函数功能变得复杂了,以前该函数的功能只是负责创建指定个数的节点,而现在是负责创建指定个数的节点还要将这些节点添加到指定父节点中。试想如果还需要一个函数的功能是创建指定个数的节点并将这些节点打印出来,那么createNodes函数我们并不能复用,只能重写一个函数了。函数的功能越单一越容易被复用,所以在很多公司都有类似的编码规定,即一个方法不能超过多少行,类不能超过多少行等,归根结底还是为了使方法和类的功能更加单一,更容易被复用。


在Java中,我们可以将createNodes方法的第二个参数传递为一个接口I。在createNodes


方法中将创建的node给接口I的一个方法xxNode(node),至于要将node用来做什么,就看使用者对接口I方法xxNode(node)的具体实现了。这样便很好的实现了代码的复用。但是在


Java中,这样定义好createNodes(sum, I)方法之后,我们并不能将接口O作为第二个参数


传递给createNodes(sum, I)方法,这似乎还不够灵活,当然Java有它的解决办法,比如


让接口O继承接口I等。扯远了,回到Javascript,Javascript是弱类型语言,没有类的概


念,Javascript中的函数就是第一类对象,我们可以将任何的函数作为第二个参数传给


createNodes方法,这似乎比Java更加的灵活。


看例子:


//添加节点的函数
    var addNode = function(child_Node)
    {
        //该方法只负责向父节点添加一个子节点
        var parent_Node = document.getElementsByTagName("body")[0];
        parent_Node.appendChild(child_Node);
    };

    //创建添加子节点函数
    var createNodes = function(sum, callback)
    {
        var node, //节点
        textNode,
        nodes = [],
        i = sum;

        if (typeof callback !== "function")
        {
            //弱类型语言的好处,不用重新定义变量接受boolean值。
            callback = false;
        }

        while(i)
        {
            node = document.createElement("div");
            textNode = document.createTextNode
                         ("callback-function " + i);
            node.appendChild(textNode);
            nodes.push(node);

            //回调
            if (callback)
            {
                //只管将创建的节点给回调方法,至于它拿去做什么不用关心
                callback(node); 
            }

            i -= 1;
        }

        return nodes;
    };

    createNodes(100, addNode);
    //匿名的方式,如果addNode方法确认不用来复用,
     //我们都不用事先声明addNode方法。
    createNodes(100, function(child_Node)
    {
        var parent_Node = document.getElementsByTagName("body")[0];
        parent_Node.appendChild(child_Node);
    });



通过以上代码可以看出Javascript中回调模式的灵活性,可以传递任意方法createNodes


函数,createNodes会将创建好的节点给回调方法,至于你做什么与其无关,乃至可以不用


传回调方法,createNodes依然做好它自己的工作。Javascript中甚至都没有接口的约束。


回调函数中的this问题。


this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的


当前对象。通俗的讲this就是执行该方法的那个对象,如:


public class User()
    {
        private String name;

        public String getName()
        {
            return this.name;
        }
    }

    User user = new User();
    user.getName();



那么在执行user.getName();方法的时候,方法getName()中的this就是调用它的当user对象。但是在javascript中,没有类的概念,Javascript中的函数就是第一类对象,那么Javascript函数中的this是谁呢?


其实由于javascript的动态性(解释执行,当然也有简单的预编译过程),this的指向只


有在运行时才确定。我的理解是,Javascript的函数在哪个作用域运行的,this就是该作


用域的提供者。这个特性在给我们带来迷惑的同时也带来了编程上的自由和灵活。


看下面的例子:


var createNodes = function(sum)
    {
        /**
         * 创建nodes集合的方法如例子1,不再累述。
          */
         return nodes;
    };  

    //定义对象
    var addNodeObj = function(child_Nodes)
    {
        node: document.getElementsByTagName("body")[0],

        addNode: function(child_Node)
        {
            var i=0, max = child_Nodes.length;
            for(; i<max; i+=1)
            {
                this.node.appendChild(child_Nodes[i]);
            }
        }
     }; 

    var child_Nodes = createNodes(10); //循环创建节点集合
    addNodeObj.addNode(child_Nodes); //循环节点集合添加到父节点



由于Javascript是解释执行的,那么在执行addNodeObj.addNode(child_Nodes);


这句话的时候才会去解释执行addNodeObj对象的方法,此时方法addNode属于函数对象addNodeObj的作用域。所以addNode方法中的this就是addNodeObj对象,那this.node当然就是该对像的node属性。


再看一个回调函数的例子:


//定义对象
    var addNodeObj = function(child_Node)
    {
        node: document.getElementsByTagName("body")[0],

        addNode: function(child_Node)
        {
            this.node.appendChild(child_Node);	
        }
    }; 

    //创建添加子节点函数
     var createNodes = function(sum, callback)
    {
        //...省略不必要的代码
        if (typeof callback !== "function")
        {
            callback = false;
        }

        while(i)
        {
            //.... 
            if (callback)
            {
                callback(node); 
            }
        }
        //...
    };

    createNodes(100, addNodeObj.addNode);



可以看到,这段代码中addNodeObj.addNode函数的执行作用域被改变了,即


createNodes函数对象,所以addNode函数中的this就指向createNodes对象,然而该对像中并没有node属性,所以addNode函数执行到 this.node.appendChild(child_Node);的时候会报错。那么在Javascript中怎样来解决这样的问题,Javascript提供了两个非常有用的函数:call和apply函数。


//定义对象
    var addNodeObj = function(child_Node)
    {
        node: document.getElementsByTagName("body")[0],

        addNode: function(child_Node)
        {
            this.node.appendChild(child_Node);	
        }
    }; 

    //创建添加子节点函数
     var createNodes = function(sum, callback_obj, callback)
    {
        //...省略不必要的代码
        if (typeof callback !== "function")
        {
            callback = false;
        }

        while(i)
        {
            //.... 
            if (callback)
            {
                callback.call(callback_obj, node); 
            }
        }
        //...
    };

    createNodes(100, addNodeObj, addNodeObj.addNode);



可以看到在createNodes方法中增加了一个参数callback_obj,callback_obj对象是提供callback函数执行作用域的函数对象。


callback.call(callback_obj, node); 这句话可以理解为callback_obj.callback(node),即改变函数callback的执行作用域为callback_obj即实参addNodeObj,这样实参addNode函数中的this又指向了addNodeObj对象。


关于Javascript函数作用域、call和apply方法的使用和区别、Javascript中的this请参照其他更准确想尽的资料,这篇文主要还是讲Javascript中的回调函数模式。