搜索
首页web前端js教程详解JavaScript中的回调函数并使用它

详解JavaScript中的回调函数并使用它

Nov 03, 2020 pm 05:47 PM
javascript回调函数

详解JavaScript中的回调函数并使用它

在JavaScript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用。既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回。

因为函数是第一类对象,我们可以在JavaScript使用回调函数。在下面的文章中,我们将学到关于回调函数的方方面面。回调函数可能是在JavaScript中使用最多的函数式编程技巧,虽然在字面上看起来它们一直一小段JavaScript或者jQuery代码,但是对于许多开发者来说它任然是一个谜。在阅读本文之后你能了解怎样使用回调函数。

回调函数是从一个叫函数式编程的编程范式中衍生出来的概念。简单来说,函数式编程就是使用函数作为变量。函数式编程过去 - 甚至是现在,依旧没有被广泛使用 - 它过去常被看做是那些受过特许训练的,大师级别的程序员的秘传技巧。

幸运的是,函数是编程的技巧现在已经被充分阐明因此像我和你这样的普通人也能去轻松使用它。函数式编程中的一个主要技巧就是回调函数。在后面内容中你会发现实现回调函数其实就和普通函数传参一样简单。这个技巧是如此的简单以致于我常常感到很奇怪为什么它经常被包含在讲述JavaScript高级技巧的章节中。

什么是回调或者高阶函数

一个回调函数,也被称为高阶函数,是一个被作为参数传递给另一个函数(在这里我们把另一个函数叫做otherFunction)的函数,回调函数在otherFunction中被调用。一个回调函数本质上是一种编程模式(为一个常见问题创建的解决方案),因此,使用回调函数也叫做回调模式。

下面是一个在jQuery中使用回调函数简单普遍的例子:

//注意到click方法中是一个函数而不是一个变量
//它就是回调函数
$("#btn_1").click(function() {
    alert("Btn 1 Clicked");
});

正如你在前面的例子中看到的,我们将一个函数作为参数传递给了click方法。click方法会调用(或者执行)我们传递给它的函数。这是JavaScript中回调函数的典型用法,它在jQuery中广泛被使用。

下面是另一个JavaScript中典型的回调函数的例子:

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

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

再一次,注意到我们讲一个匿名函数(没有名字的函数)作为参数传递给了forEach方法。

到目前为止,我们将匿名函数作为参数传递给了另一个函数或方法。在我们看更多的实际例子和编写我们自己的回调函数之前,先来理解回调函数是怎样运作的。

回调函数是怎样运作的?

因为函数在JavaScript中是第一类对象,我们像对待对象一样对待函数,因此我们能像传递变量一样传递函数,在函数中返回函数,在其他函数中使用函数。当我们将一个回调函数作为参数传递给另一个函数是,我们仅仅传递了函数定义。我们并没有在参数中执行函数。我们并不传递像我们平时执行函数一样带有一对执行小括号()的函数。

需要注意的很重要的一点是回调函数并不会马上被执行。它会在包含它的函数内的某个特定时间点被“回调”(就像它的名字一样)。因此,即使第一个jQuery的例子如下所示:

//匿名函数不会再参数中被执行
//这是一个回调函数
$("#btn_1").click(function(){
    alert("Btn 1 Clicked");
});

这个匿名函数稍后会在函数体内被调用。即使有名字,它依然在包含它的函数内通过arguments对象获取。

回调函数是闭包

都能够将一个回调函数作为变量传递给另一个函数时,这个回调函数在包含它的函数内的某一点执行,就好像这个回调函数是在包含它的函数中定义的一样。这意味着回调函数本质上是一个闭包。

正如我们所知,闭包能够进入包含它的函数的作用域,因此回调函数能获取包含它的函数中的变量,以及全局作用域中的变量。

实现回调函数的基本原理

回调函数并不复杂,但是在我们开始创建并使用回调函数之前,我们应该熟悉几个实现回调函数的基本原理。

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

在前面的jQuery例子以及forEach的例子中,我们使用了在参数位置定义的匿名函数作为回调函数。这是在回调函数使用中的一种普遍的魔术。另一种常见的模式是定义一个命名函数并将函数名作为变量传递给函数。比如下面的例子:

//全局变量
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, callback){
    allUserData.push(options);
    callback(options);
}

//当我们调用getInput函数时,我们将logStuff作为一个参数传递给它
//因此logStuff将会在getInput函数内被回调(或者执行)
getInput({name:"Rich",speciality:"Javascript"}, logStuff);
//name:Rich
//speciality:Javascript

传递参数给回调函数

既然回调函数在执行时仅仅是一个普通函数,我们就能给它传递参数。我们能够传递任何包含它的函数的属性(或者全局属性)作为回调函数的参数。在前面的例子中,我们将options作为一个参数传递给了回调函数。现在我们传递一个全局变量和一个本地变量:

//全局变量
var generalLastName = "Cliton";

function getInput (options, callback){
    allUserData.push (options);
    //将全局变量generalLastName传递给回调函数
    callback(generalLastName,options);
}

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

在调用之前检查作为参数被传递的回调函数确实是一个函数,这样的做法是明智的。同时,这也是一个实现条件回调函数的最佳时间。

我们来重构上面例子中的getInput函数来确保检查是恰当的。

function getInput(options, callback){
    allUserData.push(options);
    
    //确保callback是一个函数
    if(typeof callback === "function"){
        //调用它,既然我们已经确定了它是可调用的
        callback(options);
    }
}

如果没有适当的检查,如果getInput的参数中没有一个回调函数或者传递的回调函数事实上并不是一个函数,我们的代码将会导致运行错误。

使用this对象的方法作为回调函数时的问题

当回调函数是一个this对象的方法时,我们必须改变执行回调函数的方法来保证this对象的上下文。否则如果回调函数被传递给一个全局函数,this对象要么指向全局window对象(在浏览器中)。要么指向包含方法的对象。

我们在下面的代码中说明:

//定义一个拥有一些属性和一个方法的对象 //我们接着将会把方法作为回调函数传递给另一个函数

var clientData = {
    id: 094545,
    fullName "Not Set",
    //setUsrName是一个在clientData对象中的方法
    setUserName: fucntion (firstName, lastName){
        //这指向了对象中的fullName属性
        this.fullName = firstName + " " + lastName;
    }
} 

function getUserInput(firstName, lastName, callback){
    //在这做些什么来确认firstName/lastName
    //现在存储names
    callback(firstName, lastName);
}

在下面你的代码例子中,当clientData.setUsername被执行时,this.fullName并没有设置clientData对象中的fullName属性。相反,它将设置window对象中的fullName属性,因为getUserInput是一个全局函数。这是因为全局函数中的this对象指向window对象。

getUserInput("Barack","Obama",clientData.setUserName);
console.log(clientData,fullName);  //Not Set
//fullName属性将在window对象中被初始化     
console.log(window.fullName);  //Barack Obama

使用Call和Apply函数来保存this

我们可以使用Call或者Apply函数来修复上面你的问题。到目前为止,我们知道了每个JavaScript中的函数都有两个方法:CallApply。这些方法被用来设置函数内部的this对象以及给此函数传递变量。

call接收的第一个参数为被用来在函数内部当做this的对象,传递给函数的参数被挨个传递(当然使用逗号分开)。Apply函数的第一个参数也是在函数内部作为this的对象,然而最后一个参数确是传递给函数的值的数组。

听起来很复杂,那么我们来看看使用ApplyCall有多么的简单。为了修复前面例子的问题,我将在下面你的例子中使用Apply函数:

//注意到我们增加了新的参数作为回调对象,叫做“callbackObj”
function getUserInput(firstName, lastName, callback. callbackObj){
    //在这里做些什么来确认名字
    callback.apply(callbackObj, [firstName, lastName]);
}

使用Apply函数正确设置了this对象,我们现在正确的执行了callback并在clientData对象中正确设置了fullName属性:

//我们将clientData.setUserName方法和clientData对象作为参数,clientData对象会被Apply方法使用来设置this对象     
getUserName("Barack", "Obama", clientData.setUserName, clientData);

//clientData中的fullName属性被正确的设置
console.log(clientUser.fullName); //Barack Obama

我们也可以使用Call函数,但是在这个例子中我们使用Apply函数。

允许多重回调函数

我们可以将不止一个的回调函数作为参数传递给一个函数,就像我们能够传递不止一个变量一样。这里有一个关于jQuery中AJAX的例子:

function successCallback(){
    //在发送之前做点什么
}
  
function successCallback(){
//在信息被成功接收之后做点什么
}

function completeCallback(){
//在完成之后做点什么
}

function errorCallback(){
    //当错误发生时做点什么
}

$.ajax({
    url:"http://fiddle.jshell.net/favicon.png",
    success:successCallback,
    complete:completeCallback,
    error:errorCallback
});

“回调地狱”问题以及解决方案

在执行异步代码时,无论以什么顺序简单的执行代码,经常情况会变成许多层级的回调函数堆积以致代码变成下面的情形。这些杂乱无章的代码叫做回调地狱因为回调太多而使看懂代码变得非常困难。我从node-mongodb-native,一个适用于Node.js的MongoDB驱动中拿来了一个例子。这段位于下方的代码将会充分说明回调地狱:

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();
                    });
                });
            });
        });
    });
});

你应该不想在你的代码中遇到这样的问题,当你当你遇到了

  • 你将会时不时的遇到这种情况

  • 这里有关于这个问题的两种解决方案。

  • 给你的函数命名并传递它们的名字作为回调函数,而不是主函数的参数中定义匿名函数。

  • 模块化L将你的代码分隔到模块中,这样你就可以到处一块代码来完成特定的工作。然后你可以在你的巨型应用中导入模块。

创建你自己的回调函数

既然你已经完全理解了关于JavaScript中回调函数的一切(我认为你已经理解了,如果没有那么快速的重读以便),你看到了使用回调函数是如此的简单而强大,你应该查看你的代码看看有没有能使用回调函数的地方。回调函数将在以下几个方面帮助你:

  • 避免重复代码(DRY-不要重复你自己)

  • 在你拥有更多多功能函数的地方实现更好的抽象(依然能保持所有功能)

  • 让代码具有更好的可维护性

  • 使代码更容易阅读

  • 编写更多特定功能的函数

创建你的回调函数非常简单。在下面的例子中,我将创建一个函数完成以下工作:读取用户信息,用数据创建一首通用的诗,并且欢迎用户。这本来是个非常复杂的函数因为它包含很多if/else语句并且,它将在调用那些用户数据需要的功能方面有诸多限制和不兼容性。

相反,我用回调函数实现了添加功能,这样一来获取用户信息的主函数便可以通过简单的将用户全名和性别作为参数传递给回调函数并执行来完成任何任务。

简单来讲,getUserInput函数是多功能的:它能执行具有无种功能的回调函数。

//首先,创建通用诗的生成函数;它将作为下面的getUserInput函数的回调函数

function genericPoemMaker(name, gender) {
    console.log(name + " is finer than fine wine.");
    console.log("Altruistic 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");
}

//callback,参数的最后一项,将会是我们在上面定义的genericPoemMaker函数
function getUserInput(firstName, lastName, gender, callback) {
    var fullName = firstName + " " + lastName;
    // Make sure the callback is a function
    if (typeof callback === "function") {
    // Execute the callback function and pass the parameters to it
    callback(fullName, gender);
    }
}

调用getUserInput函数并将genericPoemMaker函数作为回调函数:

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函数:

unction greetUser(customerName, sex)  {
    var salutation  = sex && sex === "Man" ? "Mr." : "Ms.";
    console.log("Hello, " + salutation + " " + customerName);
}

// 将greetUser作为一个回调函数
getUserInput("Bill", "Gates", "Man", greetUser);

// 这里是输出
Hello, Mr. Bill Gates

我们调用了完全相同的getUserInput函数,但是这次完成了一个完全不同的任务。

正如你所见,回调函数很神奇。即使前面的例子相对简单,想象一下能节省多少工作量,你的代码将会变得更加的抽象,这一切只需要你开始使用毁掉函数。大胆的去使用吧。

在JavaScript编程中回调函数经常以几种方式被使用,尤其是在现代Web应用开发以及库和框架中:

  • 异步调用(例如读取文件,进行HTTP请求,等等)

  • 时间监听器/处理器

  • setTimeoutsetInterval方法

  • 一般情况:精简代码

结束语

JavaScript回调函数非常美妙且功能强大,它们为你的Web应用和代码提供了诸多好处。你应该在有需求时使用它;或者为了代码的抽象性,可维护性以及可读性而使用回调函数来重构你的代码。

推荐教程:《JavaScript视频教程

以上是详解JavaScript中的回调函数并使用它的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:javascriptissexy。如有侵权,请联系admin@php.cn删除
Python vs. JavaScript:学习曲线和易用性Python vs. JavaScript:学习曲线和易用性Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

Python vs. JavaScript:社区,图书馆和资源Python vs. JavaScript:社区,图书馆和资源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

从C/C到JavaScript:所有工作方式从C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

JavaScript引擎:比较实施JavaScript引擎:比较实施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

超越浏览器:现实世界中的JavaScript超越浏览器:现实世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在现实世界中的应用包括服务器端编程、移动应用开发和物联网控制:1.通过Node.js实现服务器端编程,适用于高并发请求处理。2.通过ReactNative进行移动应用开发,支持跨平台部署。3.通过Johnny-Five库用于物联网设备控制,适用于硬件交互。

使用Next.js(后端集成)构建多租户SaaS应用程序使用Next.js(后端集成)构建多租户SaaS应用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务

如何使用Next.js(前端集成)构建多租户SaaS应用程序如何使用Next.js(前端集成)构建多租户SaaS应用程序Apr 11, 2025 am 08:22 AM

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

JavaScript:探索网络语言的多功能性JavaScript:探索网络语言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它们
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!