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】JavaScript回调函数

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

  3. JavaScript回调函数的理解

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

  4. 如何理解JS回调函数

    1.回调函数英文解释: A callback is a function that is passed as an argument to another function and is execut ...

  5. 理解 JS 回调函数中的 this

    任何变量或对象都有其赖以生存的上下文.如果简单地将对象理解为一段代码,那么对象处在不同的上下文,这段代码也会执行出不同的结果. 例如,我们定义一个函数 getUrl 和一个对象 pseudoWindo ...

  6. JavaScript 回调函数中的 return false 问题

    今天一个同事问了我一个问题,就是在 Ajax 方法中,请求成功后(success)的回调函数中根据响应的值来判断程序是否继续执行,他不解的是在回调函数中已经 return false 了,但是 Aja ...

  7. 理解JS回调函数

    我们经常会用到客户端与Web项目结合开发的需求,那么这样就会涉及到在客户端执行前台动态脚本函数,也就是函数回调,本文举例来说明回调函数的过程. 首先创建了一个Web项目,很简单的一个页面,只有一个bu ...

  8. 关于javascript 回调函数

    http://segmentfault.com/q/1010000000212522 如何避免Javascript中回调函数的嵌套? http://javascript.ruanyifeng.com/ ...

  9. javascript 回调函数应用

    回调函数是什么在学习之前还真不知道js回调函数怎么使用及作用了,下面本文章把我在学习回调函数例子给各位同学介绍一下吧,有需了解的同学不防进入参考. 回调函数原理: 我现在出发,到了通知你”这是一个异步 ...

随机推荐

  1. [Canvas前端游戏开发]——FlappyBird详解

    一直想自己做点小东西,直到最近看了本<HTML5游戏开发>,才了解游戏开发中的一点点入门知识. 本篇就针对学习的几个样例,自己动手实践,做了个FlappyBird,源码共享在度盘 :也可以 ...

  2. 突袭HTML5之WebGL 3D概述

    WebGL开启了网页3D渲染的新时代,它允许在canvas中直接渲染3D的内容,而不借助任何插件.WebGL同canvas 2D的API一样,都是通过脚本操纵对象,所以步骤也是基本相似:准备工作上下文 ...

  3. 在Linux上配置Zabbix的环境

    useradd -s /bin/false zabbix mkdir /usr/local/zabbix_agent mv /home/zihexin/zabbix_agents_3.2.0.linu ...

  4. 支持Cookie并开放了一些特殊设置项的HttpWebClient

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.N ...

  5. Android菜鸟成长记16 -- JSON的解析

    JSON的定义  一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性.业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当今大部分语言的支持),从而可以在不同平台间进行数据 ...

  6. freeCodeCamp:Mutations

    蛤蟆可以吃队友,也可以吃对手. 如果数组第一个字符串元素包含了第二个字符串元素的所有字符,函数返回true. 举例,["hello", "Hello"]应该返回 ...

  7. bianma 水平 技巧

    能够写出这样的代码, 其实体现了水平 switch (state) { case 0: break; case 3: return; // already connected case 4: stat ...

  8. python leetcode 日记--231. Power of Two

    题目: Given an integer, write a function to determine if it is a power of two. class Solution(object): ...

  9. 关于iphone6安装了727个应用后,更新app 导致一些app无法更新,无法删除,重启后消失,但是却还是占用空间的解决办法

    我的iphone6 苹果手机,64GB的,存储空间最近一直很吃紧,很捉急,昨天,终于下定决心 解决下这个问题. 由于 空间大,我又随便安装许多APP,现在有727个app,常用的其实就是那个几十个而已 ...

  10. 【BZOJ-1208】宠物收养所 Splay

    1208: [HNOI2004]宠物收养所 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 6638  Solved: 2601[Submit][Sta ...