梦幻泡影

孔思哲的博客空间

0%

彻底理解JS中的回调(Callback)函数

作为JS的核心,回调函数和异步执行是紧密相关的,也是必须跨过去的一道个门槛。

那么究竟什么是回调函数(Callback),网上有许多的文章,这些文章大概分成两类,第一类术语太多,看不懂。另一类反过来,太过于生活化,讲的是一些脱离编程的例子,还是看的人晕头转向。
其实回调函数并不复杂,明白两个重点即可:

1. 函数可以作为一个参数在另一个函数中被调用

2. 大多数语言都是同步编程语言,比如现在我们有3行代码,那么系统一定是一行一行按顺序向下执行的,第一行执行完了,执行第二行,紧跟着最后执行第三行,你可能会说这不是废话吗?且慢,在JS里则不尽然,比如有3行代码,并不是排在最前面的代码就是最先执行完毕的,很有可能是最后一行语句最先执行完,然后排在最前面的那行反而是最后执行完毕的,所以我们说JS是异步编程语言。

下面以node.js为例,举一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var fs = require("fs");
var c = 0

function f(x) {
console.log(x)
}

function writeFile() {
fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {
if (!err) {
c = 1
console.log("文件写入完毕!")
}
});
}

writeFile()
f(c)

以上代码不难理解,就是设置一个全局变量c = 0,然后执行writeFile函数(也就是写入一个文件input.txt),写完之后让c=1,然后再调用f()函数,f()函数简单至极,就是把打印一个变量,仅此而已。

按照正常逻辑,首先c=0,然后在调用writeFile函数的时候里面有一句c=1,我们先调用的writeFile,所以c=1肯定是会被执行到的,那么结果应该是打印1,但是万万想不到,结果是0,明明我们在writeFile函数里我们重新对c进行了赋值,为什么结果还是0呢?

因为程序运行到writeFile()这一行的时候,是一个比较耗时的IO操作,系统并不会卡在此处,死等writeFile执行完毕再执行下一条语句,而是直接下一条代码,即f(c),而此时c并没有被重新赋值为1,所以打印出来的结果还是0 ! 

这时候就需要搬出我们的主角“回调函数”了,改写一下writeFile函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fs = require("fs");

function f(x) {
console.log(x)
}

function writeFile(callback) { //关键字callback,在这里就是指f()
fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {
if (!err) {
console.log("文件写入完毕!")
c = 1
callback(c) // 此行相当于f(c)
}
});
}
var c = 0
writeFile(f) // 函数f作为一个参数传进writeFile函数

我们在writeFile函数的形参里加入了一个关键字callback,表示这是一个回调函数,也就是前面所说的重点1,即所谓的“以函数为参数”,然后当文件写入完毕后,我们执行c=1, 然后再用一次callback关键字,在这里,关键字”callback”就是f()函数的化身,表示我们在此处调用一次f()函数.

如果你看明白上面的代码,那么我们现在开始用一句话攻略做一个总结:

【在大多数编程语言中,函数的形参总是由外往内向函数体传递参数,但在JS里如果形参是关键字”callback”则完全相反,它表示函数体在完成某种操作后由内向外调用某个外部函数】
所谓的“回调”,就是回头调用的意思。本例子中即是:让我先写文件,写操作结束后,我再回头调用f()函数。

有时候,我们会看到一些回调函数并没有使用callback关键字,这种连callback关键字和函数名都省略了,直接在函数的形参中嵌入一个function的写法,在js代码中更为常见,其本质上仍然是回调函数。