理解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中的函数都有两个办法:Call
和 Apply
。这些办法被用来设定函数内部的this对象乃至给此函数传递变量。
call
接收的第一个参数为被用来在函数内部当做this
的对象,传递给函数的参数被受个传递(当然使用逗号分开)。Apply
函数的第一个参数也是在函数内部作为this
的对象,然而最后一个参数确是传递给函数的值的数组。
听起来很复杂,那么我们来看看使用Apply
和Call
有多么的简便。为了修复前面例子的问题,我将鄙人面你的例子中使用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恳求,等等)
时间监听器/处置器
setTimeout
和setInterval
办法一样状况:精简代码
完毕语
JavaScript回调函数非常美好且功效强大,它们为你的Web利用和代码供给了诸多好处。你应当在有需求时使用它;或者为了代码的抽象性,可保护性乃至可读性而使用回调函数来重构你的代码。
相关免费学习引荐:js视频教程
以上就是理解JavaScript中的回调函数并使用它们的具体内容,更多请关注百分百源码网其它相关文章!