jQuery源码分析17--回调函数

来源:互联网 发布:网络调查赚钱 编辑:程序博客网 时间:2024/06/05 06:00

理解回调函数

本章主要讲解回调函数在jQuery中的使用技巧与实现原理,概念上的东西看似简单,但是在实际运用中要做到灵活自如却也不是那么容易的事,而且对于部分开发者来说它仍然是一个谜。在阅读本文之后你能深入理解这个“回调函数”。

函数是第一类对象,这是javascript中的一个重要的概念。意味着函数可以像对象一样按照第一类管理被使用,所以在javaScript中的函数:

    ☑   能“存储”在变量中

    ☑   能作为函数的实参被传递

    ☑   能在函数中被创建

    ☑   能从函数中返回    

百科里面是这么解释的:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件条件进行响应。

因此从上面可以看出来,回调本质上是一种设计原则,并且jQuery的设计原则遵循了这个模式。

在后端的编程语言中,传统函数以参数形式输入数据,并且使用返回语句返回值。理论上,在函数结尾处有一个return返回语句,结构上就是:一个输入和一个输出。简单的理解函数本质上就是输入和输出之间实现过程的映射

但是,当函数的实现过程非常漫长,你是选择等待函数完成处理,还是使用回调函数进行异步处理呢?这种情况下,使用回调函数变得至关重要,例如:AJAX请求。若是使用回调函数进行处理,代码就可以继续进行其他任务,而无需空等。实际开发中,经常在javascript中使用异步调用。

jQuery中遍地都是回调的设计:

异步回调:

事件句柄回调

$(document).ready(callback);$(document).on(‘click’,callback)

Ajax异步请求成功失败回调

$.ajax({  url: "aaron.html",  context: document}).done(function() {         //成功执行}).fail(function() {        //失败执行);

动画执行完毕回调

$('#clickme').click(function() {    $('#book').animate({        opacity: 0.25,        left: '+=50',        height: 'toggle'    }, 5000, function() {        // Animation complete.    });});

以上都是jQuery的回调直接运用,运用基本都是将匿名函数作为参数传递给了另一个函数或方法。而且以上都有一个特点,执行的代码都是异步的。

同步回调:

当然回调不仅仅只是处理异步,一般同步(很耗时的任务)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。

一个同步(阻塞)中使用回调的例子,目的是在test1代码执行完成后执行回调callback

var test1 = function(callback) {    //执行长时间操作    callback();}test1(function() {    //执行回调中的方法});

 

所以理解回调函数最重要的2点:

1、一个回调函数作为参数传递给另一个函数是,我们仅仅传递了函数定义。我们并没有在参数中执行函数。我们并不传递像我们平时执行函数一样带有一对执行小括号()的函数

2、回调函数并不会马上被执行,它会在包含它的函数内的某个特定时间点被“回调”。

回调的灵活运用

我们经常会这样使用函数回调:

     事件触发通知

     资源加载通知

     定时器延时

     ajax、动画通知等等。

以上都是很单一的事件监听回调的处理方式,但是jQuery把回调函数的用法设计成一个更高的抽像,用于解耦与分离变化。

如何理解这个设计?我们看下面的例子。

例子一:

jQuery针对Dom的处理提供了append、prepend、before、after等方法的处理,这几个方法的特征:

1、参数的传递可以是HTML字符串、DOM元素、元素数组或者jQuery对象

2、为了优化性能针对节点的处理需要生成文档碎片

可见几个方法都是需要实现这2个特性的,那么我们应该如何处理?

高层接口:

before: function() {    return this.domManip(arguments, function(elem) {        if (this.parentNode) {            this.parentNode.insertBefore(elem, this);        }    });},after: function() {    return this.domManip(arguments, function(elem) {        if (this.parentNode) {            this.parentNode.insertBefore(elem, this.nextSibling);        }    });},

底层实现:

domManip: function(args, callback) {    // Flatten any nested arrays    args = concat.apply([], args);    // We can't cloneNode fragments that contain checked, in WebKit    if (isFunction ||        //多参数处理        self.domManip(args, callback);    }    if (l) {        //生成文档碎片        fragment = jQuery.buildFragment(args, this[0].ownerDocument, false, this);        callback.call(this[i], node, i);    }    return this;}

我们观察下jQuery的实现,通过抽象出一个domManip方法,然后在这个方法中处理共性,合并多个参数的处理与生成文档碎片的处理,然后最终把结果通过回调函数返回给每一个调用者。

例子二:

在很多时候需要控制一系列的函数顺序执行。那么一般就需要一个队列函数来处理这个问题。

我们看一段代码:

function Aaron(List, callback) {    setTimeout(function() {        var task;        if (task = List.shift()) {            task(); //执行函数        }        if (List.length > 0) { //递归分解            arguments.callee(List)        } else {            callback()        }    }, 25)}//调用​Aaron([    function() {        alert('a')    },    function() {        alert('b')    },    function() {        alert('c')    }], function() {    alert('callback')})// 分别弹出 ‘a’ , ‘b’ ,'c',’callback

传入一组函数参数,靠递归解析,分个执行,其实就是靠setTimeout可以把函数加入到队列末尾才执行的原理,这样的写法就有点就事论事了,聚合对象完全是一个整体,无法再次细分出来,所以我们需要一种方案,用来管理分离每一个独立的对象。

我们换成jQuery提供的方式:

var callbacks = $.Callbacks();callbacks.add(function() {    alert('a');})callbacks.add(function() {    alert('b');})callbacks.fire(); //输出结果: 'a' 'b'

是不是便捷很多了,代码又很清晰,所以Callbacks它是一个多用途的回调函数列表对象,提供了一种强大的方法来管理回调函数队列。

那么我们使用回调函数,总的来说弱化耦合,让调用者与被调用者分开,调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件的被调用函数。


0 0
原创粉丝点击