JavaScript中,函数是一等(first-class)对象;也就是说,函数是 Object 类型并且可以像其他一等对象(String,Array,Number等)一样使用。它们可以“保存在变量中,作为参数传递给函数,在函数内创建,以及被函数返回”。

由于函数是一等对象,我们可以把一个函数作为参数传递给另一个函数,然后在那个函数内执行,甚至也可以被那个函数返回,然后再执行。这就是 JavaScript 中回调函数(callback functions)的本质。在本文的剩余部分,我们将学习到关于 JavaScript 回调函数的所有知识。回调函数可能是 JavaScript 中使用最广泛的函数式编程技术了,你可以在任何一段 JavaScript或jQuery 代码发现它,但是,它对很多 JavaScript 开发者来说依然是神秘的。直到你阅读了本文,就再也不会对它感到神秘了。

回调函数 是来源于函数式编程的一种技术。从底层来说,函数式编程把函数用作参数。函数式编程过去是 —— 现在仍然是,(尽管如今不太流行)被有经验的、高级开发者视作难懂的技术。

幸运的是,函数式编程已经被阐明到像我们这样的的普通人都能容易理解的地步。其主要技术之一就是回调函数。下面你就会看到,实现回调函数很容易,就像传递一个普通变量参数一样。这个技术如此简单以至于我很奇怪为什么大多数教程都把它归类为高级主题里面。

回调是什么?

回调函数,也叫高阶(higher-order)函数,是一个作为参数传递到其他函数的函数,然后回调函数在其他函数中被调用。回调函数本质上是一个模式,因此使用回调函数被称为回调模式。

请看下面的代码,这是 jQuery 中常见的回调函数的使用:

$("#btn_1").click(function(){
alert("Btn 1 Clicked");
});

这里,传递了一个匿名函数给 click 方法。click 方法将调用或执行这个回调函数。

再看一个例子:

var friend = ["Mike", "Stacy", "Andy", "Rick"];

friend.forEach(function(eachName, index){
console.log(index + 1 + "." + eachName); // 1.Mike,2.Stacy,3.Andy,4.Rick
});

这里,传递了一个匿名函数给 forEach 方法。

回调函数如何工作?

当我们传递一个回调函数给其他函数时,我们只是传递了函数定义。我们并没有在参数中执行函数。换句话说,传递函数时不能在函数名后面加括号“()”,而执行函数时那样需要。

由于其他函数在参数中有该回调函数的定义,所以它可以在任何时候执行该函数。

注意到回调函数不是立即执行的,所谓“回调”就是指在其他函数中某个特定的时候被回头调用。所以再看第一个例子,click 函数中的匿名函数将在click函数体内被调用。即使该函数匿名,也可以通过 arguments 对象访问到。

回调函数都是闭包

当传递一个回调函数给其他函数时,回调函数在其他函数函数体内某处执行,就好像回调函数是在其他函数中定义的。这说明回调函数是一个闭包(closure)。众所周知,闭包可以访问外层包含函数的作用域,所以回调函数也能访问其他函数的变量,甚至全局作用域中的变量。

实现回调函数所遵循的基本准则

尽管不复杂,但仍然有一些值得注意的地方。

使用命名的或匿名的函数作为回调函数

第二个例子中我们使用了匿名函数作为回调函数,这是一种常见的做法。另一种常见做法是定义一个有名字的函数,然后传递给另一个函数。

// 全局变量
var allUserData = []; // logStuff 函数,用于打印参数的值
function logStuff(userData){
if(typeof userData === "string"){
console.log(userData);
}
else if(typeof userData === "object"){
for(var item in userData){
console.log(item + ":" + userData[item]);
}
}
} // 该函数接受两个参数,第二个参数是回调函数
function getInput(options, calllback){
allUserData.push(options);
calllback(options);
} // 调用 getInput 函数时传递了 logStuff,所以,logStuff 将在 getInput 函数中被调用(或执行)
getInput({name:"Rich",speciality:"JavaScript"},logStuff);
// name:Rich
// speciality:JavaScript

传递参数给回调函数

由于回调函数在执行时也只是一个普通函数,所以我们可以传递参数给它。可以传递外层函数的属性或者全局变量。上例中,传递了 options 作为回调函数的参数。然后,让我们传递一个全局变量和局部变量。

// 全局变量
var generalLastName = "Clinton"; function getInput(options, calllback){
allUserData.push(options);
// 传递全局变量
calllback(generalLastName, options);
}

执行前确保回调是一个函数

在调用之前检查所传入的回调函数是否真的是一个函数是一个好习惯。让我们重构一下上例中getInput函数:

function getInput(options, calllback){
allUserData.push(options); // 确保 calllback 是一个函数
if(typeof calllback === "function"){
// 调用之,因为已经确保它是函数了
calllback(options);
}
}

如果不检测其类型,当传入的参数不是函数时,就会导致运行时错误。

回调函数与this相关的问题

当回调函数中用到了 this 对象时,我们不得不改变执行回调函数的方式来保持原有的 this 对象。否则,this 对象可能指向全局的 window 对象,如果回调函数被传入了全局函数中的话。或者指向外层包含方法的对象。

下面在代码中演示:

var clientData = {
id: 012334,
fullName: "Not Set",
// setUserName 是 clientData 对象的方法
setUserName: function(firstName, lastName){
this.fullName = firstName + " " + lastName;
}
} function getUserInput(firstName, lastName, calllback){
calllback(firstName,lastName);
}

下面的代码中,当 clientData.setUserName 执行时,this.fullName 将不会设置 clientData 对象的 fullName 属性,而是设置为 window 对象的 fullName 属性。这是因为全局函数中的 this 对象指向 window 对象

getUserInput("Barack", "Obama", clientData.setUserName);

console.log(clientData.fullName); // Not Set

console.log(window.fullName); // Barack Obama

使用 CallApply 函数来保持 this

我们可以通过 CallApply 函数来解决上面的问题。目前来说,JavaScript 中的每个函数都有两个方法:Call 和 Apply。这两个方法用来设置函数内的 this 对象。

Call 把第一个参数作为函数内的 this 对象,其他的参数分别传递给函数。Apply 也是把第一个参数作为函数内的 this 对象,而第二个参数是一个数组(或者argument对象)。

我们在下面的代码中使用 Apply 来解决这个问题:

function getUserInput(firstName, lastName, calllback, calllbackObj){
calllback.apply(calllbackObj, [firstName,lastName]);
}

apply 正确设置了 this 对象,现在可以正确执行回调,并设置 clientData 上的 fullName 属性了。

getUserInput("Barack", "Obama", clientData.setUserName, clientData);

console.log(clientData.fullName); // Barack Obama

允许使用多个回调函数

我们传递多个回调函数到另外函数,就像传递多个变量一样,下面时一个经典的jQuery AJAX函数的例子:

function successCallBack(){

}

function failCallBack(){

}

function completeCallback(){

}

function errorCallback(){

}

$.ajax({
url:"http://www.91ymb.com/favicon.png",
success: successCallBack,
complete: completeCallback,
error: errorCallback
});

“回调地狱”问题和解决

在异步代码执行中,代码可能以任何顺序执行,有时会看到有很多层回调函数,比如下例:

var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory});
p_client.open(function(err, p_client) {
p_client.dropDatabase(function(err, done) {
p_client.createCollection('test_custom_key', function(err, collection) {
collection.insert({'a':1}, function(err, docs) {
collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) {
cursor.toArray(function(err, items) {
test.assertEquals(1, items.length);

// Let's close the db​
p_client.close();
});
});
});
});
});
});

以上杂乱的代码被称作回调地狱,回调太多以至于很难理解。你可能不会遭遇这个问题,但是如果遇到了,这有两种方法解决这个问题。

  1. 命名函数,定义函数,然后传递函数名作为回调,而不是定义匿名函数。
  2. 模块化:把代码分模块,这有就可以导出某个特定任务的代码。然后导入那个特定模块。

写你自己的回调函数

到现在,你应该理解了关于 JavaScript 回调函数的所有内容,你发现使用回调函数不仅简单而且很强大,你应该看看自己的代码,寻找一些机会使用回调函数,它能让你做这些事情:

  1. 不重复代码(DRY)
  2. 实现更好的抽象。
  3. 更好的可维护性
  4. 更好的可读性
  5. 更多专门的函数

写自己的回调函数也很简单。下面的例子中,我将创建一个函数用来:取回用户数据,使用数据生成诗句,然后告诉用户。这听起来好像是一个杂乱的函数,有很多if/else语句,并且可能被限制而不能用用户数据做些其他的事情。

但是,我把具体功能的实现交给回调函数,这样主函数用于取回用户数据,然后简单地传递用户全名和性别给回调函数,然后执行回调函数就行了。

简言之,getUserInput 函数是通用的:它执行所有回调函数来实现具体的功能。

// 首先,建立一个诗句生成函数,它将作为回调函数传递
function genericPoemMaker(name, gender){
console.log(name + " is finer than fine wine.");
console.log("ALltrustic and noble for the modern time.");
console.log("Always admirably adorned with the latest style.");
console.log("A " + gender + " of unfortunate tragedies who still manages a perpetual smile");
} //
function getUserInput(firstName, lastName, gender, callback){
var fullName = firstName + " " + lastName; // 确保 callback 是函数
if(typeof callback === "function"){
callback(fullName, callback);
}
} // 调用 getUserInput,并传递回调函数
getUserInput("Michael", "Fassbender", "Man", genericPoemMaker);
// 输出
​/* Michael Fassbender is finer than fine wine.
Altruistic and noble for the modern time.
Always admirably adorned with the latest style.
A Man of unfortunate tragedies who still manages a perpetual smile.
*/

由于 getUserInput 函数只是处理数据,我们可以传递任何回调函数。例如,传递一个 greetUser 函数:

function greetUser(customerName, sex){
var salutation = sex & sex == "Man" ? "Mr." : "Ms.";
console.log("Hello, " + salutation + " " + customerName);
} getUserInput("Bill", "Gates", "Man", greetUser); // 输出
// Hello, Mr. Bill Gates

我们同样调用 getUserInput 函数两次,但执行了不同的任务。

注意到,下列场景是我们频繁使用到回调函数的地方,尤其是现代 web 应用开发,库和框架开发:

  1. 异步执行(如读取文件,HTTP请求)
  2. 事件监听器/处理器
  3. setTimeout 函数和 setInterval 函数
  4. 通用原则:代码简洁性

最后

JavaScript 回调函数很好用,有很多好处。现在就开始使用回调函数来重构代码以提高抽象、可维护性、可读性吧。

原文链接: http://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/

理解 JavaScript 回调函数并使用的更多相关文章

  1. 理解javascript 回调函数

    ##回调函数定义 百度百科:回调函数 回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数.回调函数不 ...

  2. 重新理解javascript回调函数

    把函数作为参数传入到另一个函数中.这个函数就是所谓的回调函数 经常遇到这样一种情况,某个项目的A层和B层是由不同的人员协同完成.A层负责功能funA,B层负责funcB.当B层要用到某个模块的数据,于 ...

  3. JavaScript回调函数的实现

    https://github.com/forsigner/blog/blob/master/source/_posts/javascript-callback.md 在JavaScript中,回调函数 ...

  4. 【JavaScript】JavaScript回调函数

    什么是Javascript 回调函数? 函数和其他数据一样可以被赋值,删除,拷贝等,所以也可以把函数作为参数传入到另一个函数中. 这个函数就是所谓的回调函数   举例: //不带参数的case fun ...

  5. JavaScript回调函数及数组方法测试

    JavaScript回调函数及数组方法测试 具体代码如下: <!DOCTYPE html> <html lang="en"> <head> &l ...

  6. 简单理解js回调函数

    前言 其实回调函数简单通俗点就是当有a和b两个函数,当a作为参数传给b,并在b中执行,这时a就是一个回调(callback)函数,如果a是一个匿名函数,则为匿名回调函数那下面们来通过一个实例来具体解释 ...

  7. 如何定义 Java 的回调函数,与 JavaScript 回调函数的区别

    JavaScript 中的回调函数 在 JavaScript 中经常使用回调函数,比如:get 请求.post 请求等异步任务.在我们请求之前以及请求之后,都需要完成一些固定的操作,比如:请求之前先从 ...

  8. JavaScript回调函数的理解

    这里是个人对回调函数的一段理解 <!DOCTYPE html> <html> <head> <title>回调函数</title> < ...

  9. Javascript 回调函数理解---二娃子买肾机6

    在Javascript中什么是回调函数,我认为简单来说就是把一个函数B作为参数传递给另一个函数A,在A函数中的一定时机调用函数B. 这里可以看出回调函数形成了一个闭包,它可以访问函数A中的活动对象. ...

随机推荐

  1. 通过pycharm使用git[图文详解]

    前言 使用git+pycharm有一段时间了,算是稍有点心得,这边整理一下,可能有的方法不是最优,欢迎交流,可能还是习惯敲命令去使用git,不过其实pycharm已经帮忙做了很多了,我们可以不用记住那 ...

  2. Objective-C集合总结

    Objective-C里面的集合主要包括:NSString,NSMutableString,NSArray,NSMutableArray,NSDictionary,NSMutableDictionar ...

  3. 如果你也会C#,那不妨了解下F#(6):面向对象编程之“类”

    前言 面向对象的思想已经非常成熟,而使用C#的程序员对面向对象也是非常熟悉,所以我就不对面向对象进行介绍了,在这篇文章中将只会介绍面向对象在F#中的使用. F#是支持面向对象的函数式编程语言,所以你用 ...

  4. PyQt4入门学习笔记(二)

    之前第一篇介绍了pyqt4的大小,移动位置,消息提示.这次我们介绍菜单和工具栏 QtGui.QmainWindow这个类可以给我们提供一个创建带有状态栏.工具栏和菜单栏的标准的应用. 状态栏 状态栏是 ...

  5. sql 分组取最新的数据sqlserver巧用row_number和partition by分组取top数据

    SQL Server 2005后之后,引入了row_number()函数,row_number()函数的分组排序功能使这种操作变得非常简单 分组取TOP数据是T-SQL中的常用查询, 如学生信息管理系 ...

  6. .NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和使用

    在我们开发的很多分布式项目里面(如基于WCF服务.Web API服务方式),由于数据提供涉及到数据库的相关操作,如果客户端的并发数量超过一定的数量,那么数据库的请求处理则以爆发式增长,如果数据库服务器 ...

  7. C#多态“说来也说”——逻辑层BLL中的多态使用

    本文版权归博客园和作者吴双本人共同所有.欢迎转载,转载和爬虫请注明原文地址 http://www.cnblogs.com/tdws/p/5861842.html 昨天晚上,有个朋友说学了好久,依然没搞 ...

  8. Android Studio同时打开多个项目

    Android Studio的默认设置是打开第二个项目时,第一个项目就被自动关闭了,如果要同时打开多个项目,可以点击File->Settings,对Project Opening进行下面的设置: ...

  9. 浅谈JDBC访问MySQL数据库

    经过我自己的总结后,其实很简单,只需要记住四个步骤,JDBC这部分的学习就可以掌握差不多了,请多多指教. 加载注册JDBC驱动: 打开数据库: 创建向数据库发送sql语句的statement: Res ...

  10. Spring学习系列(一) Spring简介

    Spring简介 之前一直想写点东西,可一直没有开始实施,各种原因都有,最大原因可能还是自己太懒了,嘿嘿.最近在看Spring in action这本书,为了能让自己坚持下去把书看完,这次决定同步的在 ...