JavaScript - 回调函数

这里我想跟你聊聊我理解的回调函数(callback)。

回调函数,我觉得可以理解成作为参数传递的函数对象。因为在 JavaScript 中函数有比较高的等级(是一种基本类型,嗯,应该可以这样讲吧),可以独立地进行使用。

而在 Java 中,可能只有作为类的方法的“函数”存在吧,不存在可以单独使用的函数。当然在 Java 中,我“揣测”函数,或者叫方法(method)更合适,可能只有被调用的使用方式,例如: foo.method()

在 JavaScript 中,函数还是挺独立的。例如,尽管有的时候一个函数在声明的时候就是作为某个对象的方法的形式,但仍然可以撇开这个对象来使用。这里用到了函数的 .call().apply() 方法(这里就叫方法吧,不再绕来绕去了)。例如:

var Person = function (name) {
    this.name = name;
    this.getName = function () {
        return this.name;
    };
};

我先用自己的话跟你大致说下上面的代码里面做了什么事情,有些东西是我之前的文章还没有讲到的,不过相信你可能也都懂的:

  1. 首先,我声明了一个变量和一个函数,函数本身没有名称,将其作为值赋给了变量 Person,这是最主要的结构;

  2. 然后,再来看这个匿名函数的内部,我给 this 对象(且这么叫吧)设置了两个属性,属性 name 的值为匿名函数的参数 name,属性 getName 的值为一个匿名函数;

  3. 最后,我们看下 getName 对应的匿名函数干了什么:它返回了 this 的属性 name 的值。

对于 Person 的使用方式,之前提到过(不过定义的形式可能稍有不同,这不是重点),可以这样用:

var me = new Person("luobo");
me.getName(); // "luobo"

这里说了挺多题外的东西,现在我们来看上面 me 这个对象的方法 getName()。尽管形式上是对象的方法,而且在一开始定义的时候就确定了其方法的目的(这种 this.foo = ... 的方式我会在讲继承的时候再跟你多聊一些),但是由于其函数的本质,仍然可以单独来使用:

me.getName.call({ name: "Mickey" }); // "Mickey"

注意,这里 me.getName 只是为了引用到实际的函数(对象),以 .call() 来调用时传入了新的“上下文对象” { name: "Mickey" },而这个上下文对象在函数执行时会替换函数内部使用的 this,有点了解了吗?

所以,实际上对于上文中声明的 Person 所对应的匿名函数,也可以这样来使用:

var me = {};
Person.call(me, "luobo");
me.getName(); // "luobo"

再引申一点来讲,使用 new Person() 这样的形式来获得一个对象,那么在 Person 内部的 this 就是对应这个即将返回的对象 。(严格来说不完全是,在 Person 对应的匿名函数有明确定义的其他返回值时就不再返回这个 this 对象啦)

虽然是要讲回调函数,可是我花了好多时间来讲每个函数都可以被独立调用这件事,还说到函数中的 this 在函数执行时可以明确指定(通过 .call() 传入的第一个参数即作为函数内部 this 所指向的对象),这些都是会用到的。

下面我举个使用回调函数的栗子:

$("#myDiv").on("click", function () {
    this.innerHTML = "you clicked me!";
});

这个栗子中,我们使用 jQuery 给一个 id 为 myDiv 的元素绑定了 click 事件的处理程序,这里的事件处理程序就是一个回调函数。当然回调函数并非是一种特殊类型的对象,其实就是普通的函数,但是被作为其他函数的参数传递,在某个时刻才会被选择性地使用。

单纯看回调函数不是特别有趣,不过你应该注意到上面这个回调函数的定义中使用 this,那么这个 this 又是指向什么对象呢?

实际上,这是由 jQuery 提供的机制,在事件处理程序被调用时,this 会指向事件的目标对象,这里也就是被点击的 myDiv 元素,而 HTML 元素有 innerHTML 属性,就是上面的使用方式了。这是怎么实现的呢,合理揣测下的话,肯定是这个回调函数在调用是被显式指定了“上下文对象” this,有可能就是通过 .call().apply() 的方式。

另外,由于回调函数会在什么时候被执行,可能是不确定,甚至也可能永远不会被执行。而且在上面的这种情况下,这个回调函数甚至要在未来的某个时候才会执行,这就有了一个“异步”的感觉,也就是说代码不是从上到下依次执行,前面的代码执行完毕后面的才会执行(这个可以叫做“同步”啦)。

在使用 Ajax 时,我们可以指定请求是同步还是异步的方式,同步的方式下比较好理解,一定是这个请求接收到服务器响应或者超时失败后,后面的代码才会执行。这种情况下我们编写需要在一个 Ajax 请求后才能做的时候,例如 alert("Ajax 请求已完成!"),是比较容易的。不过坏处是代码在执行到这里的时候会等待 Ajax 请求被响应,一切都停了下来。如果不想让世界暂停下来,就可以使用 Ajax 异步的模式(这也是通常的使用方式),然后把想要做的事情以回调函数的形式包装一下,等待请求在未来有个结果(响应、超时、意外终止等)后执行。举个栗子:

$.ajax("/foo.jsp").done(function () {
    alert("成功!");
}).fail(function () {
    alert("失败!");
});

这里分别为请求成功和失败两种情况指定了回调函数。

关于回调函数,咱们就聊这么多吧。

扩展:

  • 关于“异步”执行,有没有考虑过专门去使用它的情况?
    例如,代码执行时间预期会很长,为避免浏览器不响应用户交互,人为地将代码分为多个部分分别执行,从而使得每个部分执行时间不会很长。
  • 主动地让代码“异步”执行的方法?
    常见的有 setTimeout,另外还有借助浏览器的事件机制实现的,如为新创建的 img 元素指定 onload 事件的方式等等。
  • 异步执行的代码多层嵌套的处理
    因为要以回调函数的方式来编写异步执行代码,可能会很多级匿名函数互相嵌套的情况,代码可读性会比较差。这种情况下一个选择是可以使用一些第三方的库,如 when.js。其实 jQuery 也提供了异步机制,可以看下 deffered,我们常用的 jQuery 的 ajax 函数也基于这个东东改造过。

接下来还想跟你聊聊:

  • JavaScript - 作用域和“闭包”
  • JavaScript - 继承和“类”
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,780评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,424评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,397评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,576评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,997评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,945评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,107评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,850评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,625评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,804评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,285评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,613评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,291评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,164评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,963评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,096评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,886评论 2 278

推荐阅读更多精彩内容

  • 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用...
    小沙鹰168阅读 492评论 0 1
  • 什么是回调或高级函数 在JavaScrip中,function是内置对象,它可以存储在变量中,通过参数传递给另一个...
    另一只小白阅读 697评论 0 4
  • 五十三:请解释 JavaScript 中 this 是如何工作的。1.方法调用模式当一个函数被保存为一个对象的属性...
    Arno_z阅读 525评论 0 2
  • 我们先来看看回调的英文定义:A callback is a function that is passed as ...
    迈克高阅读 1,568评论 0 3
  • 砖家与杂家的区别 ①砖家专注于一件事,被人称为工匠 ②杂家来回于N件事,被人称为投机取巧 天下之大,唯有专注 ①阳...
    阳光创客敖伟伟阅读 147评论 0 0