在之前的文章当中,我们已经介绍了函数的参数与arguments对象,那么今天的分享中,我们继续来看看函数在JavaScript的各种不同面貌——回调函数。

回调函数

你可能常听到人家在谈论回调函数(Callback function),但你真的知道回调函数是什么吗?其实回调函数跟一般的函数没什么不同,差别只在于被调用执行的时机。

先前介绍事件的时候有说过,「JavaScript 是一个事件驱动(Event-driven) 的编程语言」,而事件的概念就如同:

办公室电话响了(事件被触发Event fired) -> 接电话(处理事件Event Handler)

而写成代码形式就类似:

// 注:这里只是比喻,并没有电话响这个事件
Office.addEventListener( '电话响', function 接电话(){ }, false);

可以看到,Office通过addEventListener方法去注册了一个事件,当这个事件被触发时,它会去执行我们所指定的第二个参数,也就是某个「函数」(接电话)。

换句话说,这个函数只会在满足了某个条件才会被动地去执行,我们就可以说这是一个回调函数

经历过各种「事件」的你,想必也发现了一件事,所谓的回调函数其实就是「把函数作为参数传递给另一个函数,然后通过另一个函数来调用它」。

不知大家有没有听懂?那我再讲一个简单案例吧:

window.setTimeout( function(){ ... }, 1000);

如果我们希望隔某段时间之后,执行某件事,就可以通过window.setTimeout来帮助我们完成。

像是上面的范例中,window.setTimeout带有两个参数,第一个是要做的事情,用一个函数来代表,第二个则是时间(1/1000秒, milliseconds)。而第一个参数的函数也是回调函数的一种:「在经过了某段时间之后,才执行的函数」。

当然,上面两个范例中的回调函数我们也可以把它单独抽出来定义:

var handler = function 接电话() {
};

Office.addEventListener( '电话响', handler, false);

这样会使你的代码看起来更清楚。

除了事件以外,还有另一个会需要用到回调函数的场景,就是「控制多个函数间执行的顺序」。

啥意思呢?来看个简单的例子:

var funA = function(){
console.log('function A');
};

var funB = function(){
console.log('function B');
};

funA();
funB();

因为funcAfuncB都会立即执行,并且JavaScript 的单线程模型决定了同时只能执行一个任务,其他任务都必须在后面排队等待,所以执行结果必定为:

function A
function B

但是,如果funcA是异步操作,funcB会立即执行,不会等到funcA结束再执行:

var funA = function(){
window.setTimeout(function(){
console.log('function A');
}, 1000);
};

var funB = function(){
console.log('function B');
};

funA();
funB();

像这种时候,为了确保先执行funcA,就可以通过回调函数的形式来进行处理:

// 为了确保先执行 funA 再执行 funB
// 我们在 funA 加上 callback 参数
var funA = function(callback){
window.setTimeout(function(){
console.log('function A');

// 如果 callback 是个函数就调用它
if( typeof callback === 'function' ){
callback();
}

}, 1000);
};

var funB = function(){
console.log('function B');
};

// 将 funB 作为参数带入 funA()
funA( funB );

像这样,无论funA在执行的时候要等多久,funB都会等到console.log('function A');之后才执行。

不过需要注意的是,我们可以将不止一个的回调函数作为参数传递给一个函数,就像我们能够传递不止一个变量一样,过多的异步编程嵌套在一起产生的多重回调函数甚至叫回调地狱,维护起来真的如名字一样会很可怕!

多重回调函数就类似下面这样的形式:

function async(arg, callback) {
console.log('参数为 ' + arg +' , 1秒后返回结果');
setTimeout(function () { callback(arg * 2); }, 1000);
}

function final(value) {
console.log('完成: ', value);
}

async(1, function (value) {
async(2, function (value) {
async(3, function (value) {
async(4, function (value) {
async(5, function (value) {
async(6, final);
});
});
});
});
});

执行结果为:

参数为 1 , 1秒后返回结果
参数为 2 , 1秒后返回结果
参数为 3 , 1秒后返回结果
参数为 4 , 1秒后返回结果
参数为 5 , 1秒后返回结果
参数为 6 , 1秒后返回结果
完成: 12

上面代码中,六个回调函数的嵌套,不仅写起来麻烦,容易出错,而且难以维护。

如果真的不幸需要写到这么多层,这点后续我们介绍到Promise时会再说明如何摆脱这种困境。

如果觉得文章对你有些许帮助,欢迎在我的GitHub博客点赞和关注,感激不尽!



JavaScript  

JavaScript Callback function

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!