JavaScript에서 함수는 일급 개체입니다. 즉, 일급 관리 기능을 사용하면 함수를 개체처럼 사용할 수 있습니다. 함수는 실제로 객체이기 때문에 변수에 "저장"되고, 함수 매개변수로 전달되고, 함수 내에서 생성되고, 함수에서 반환될 수 있습니다.
함수는 일급 객체이기 때문에 JavaScript에서 콜백 함수를 사용할 수 있습니다. 다음 기사에서는 콜백 함수에 대해 모두 알아봅니다. 콜백 함수는 아마도 JavaScript에서 가장 많이 사용되는 함수형 프로그래밍 기술일 것입니다. 문자 그대로 JavaScript 또는 jQuery 코드의 작은 조각처럼 보이지만 여전히 많은 개발자에게는 미스터리입니다. 이 글을 읽고 나면 콜백 함수를 사용하는 방법을 이해할 수 있습니다.
콜백 함수는 함수형 프로그래밍이라는 프로그래밍 패러다임에서 파생된 개념입니다. 간단히 말해서, 함수형 프로그래밍은 함수를 변수로 사용하는 것입니다. 함수형 프로그래밍은 과거에도 지금도 널리 사용되지는 않습니다. 예전에는 특별히 훈련받은 마스터 수준의 프로그래머를 위한 비밀 기술로 여겨졌습니다.
다행히 함수 프로그래밍 기법에 대한 설명이 이제 나와 같은 일반인들도 쉽게 사용할 수 있게 설명이 되었습니다. 함수형 프로그래밍의 주요 트릭 중 하나는 콜백 함수입니다. 다음 내용에서는 콜백 함수를 구현하는 것이 실제로 매개변수를 일반 함수에 전달하는 것만큼 간단하다는 것을 알 수 있습니다. 이 팁은 너무 간단해서 왜 고급 JavaScript 기술에 대한 장에 자주 포함되는지 궁금합니다.
고차 함수라고도 하는 콜백 함수는 다른 함수에 매개변수로 전달되는 함수입니다. otherFunction
) 함수에서 콜백 함수는 otherFunction
에서 호출됩니다. 콜백 함수는 본질적으로 프로그래밍 패턴(일반적인 문제에 대해 만들어진 솔루션)이므로 콜백 함수를 사용하는 것을 콜백 패턴이라고도 합니다. 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
//全局变量 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이전 예에서 볼 수 있듯이 함수를
click
메서드에 매개 변수로 전달했습니다. click
메소드는 우리가 전달한 함수를 호출(또는 실행)합니다. 이는 JavaScript에서 콜백 함수의 일반적인 사용 방식이며 jQuery에서도 널리 사용됩니다. 다음은 JavaScript의 일반적인 콜백 함수의 또 다른 예입니다. //全局变量 var generalLastName = "Cliton"; function getInput (options, callback){ allUserData.push (options); //将全局变量generalLastName传递给回调函数 callback(generalLastName,options); }
forEach
메서드에 매개 변수로 전달했다는 점에 유의하세요. 지금까지 우리는 익명 함수를 다른 함수나 메서드에 매개변수로 전달했습니다. 보다 실용적인 예제를 살펴보고 자체 콜백 함수를 작성하기 전에 콜백 함수의 작동 방식을 이해해 보겠습니다.
()
를 사용하여 함수를 전달하지 않습니다. 주의해야 할 중요한 점은 콜백 함수가 즉시 실행되지 않는다는 것입니다. 포함된 함수 내의 특정 시점에 (이름에서 알 수 있듯이) "콜백"됩니다. 따라서 첫 번째 jQuery 예제는 다음과 같습니다. function getInput(options, callback){ allUserData.push(options); //确保callback是一个函数 if(typeof callback === "function"){ //调用它,既然我们已经确定了它是可调用的 callback(options); } }이 익명 함수는 나중에 함수 본문 내에서 호출됩니다. 이름이 있더라도 포함 함수 내의
arguments
개체를 통해 계속 사용할 수 있습니다.
//全局变量 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
对象要么指向全局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
函数来修复上面你的问题。到目前为止,我们知道了每个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의 콜백 함수 이해 및 사용의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!