- 浏览: 62390 次
文章分类
最新评论
javascript回调函数(模式)原理和示例深入分析
广大网友读懂了我之前论述的javascript原理这篇文章很容易懂
回调函数来自一种著名的编程范式——函数式编程,在基本层面上,函数式编程指定的了函数的参数。函数式编程虽然现在的使用范围变小了,但它一直被“专业的聪明的”程序员看作是一种难懂的技术,以前是这样,未来也将是如此。
幸运的是,函数式编程已经被阐述的像你我这样的一般人也能理解和使用。函数式编程最主要的技术之一就是回调函数,你很快会阅读到,实现回调函数就像传递一般的参数变量一样简单。这项技术如此的简单,以至于我都怀疑为什么它经常被包含在JavaScript的高级话题中去。
什么是回调或高级函数?
回调函数被认为是一种高级函数,一种被作为参数传递给另一个函数(在这称作"otherFunction")的高级函数,回调函数会在otherFunction内被调用(或执行)。回调函数的本质是一种模式(一种解决常见问题的模式),因此回调函数也被称为回调模式。
思考一下下面这种在jQuery中常用的回调函数:
//Note that the item in the click method's parameter is a function, not a variable.
//The item is a callback function
$("#btn_1").click(function() {
alert("Btn 1 Clicked");
});
正如在前面的例子所看到的,我们传递了一个函数给click方法的形参,click方法将会调用(或执行)我们传递给它的回调函数。这个例子就给出了JavaScript中使用回调函数的一个典型方式,并广泛应用于jQuery中。
细细体味一下另一个基本JavaScript的典型例子:
幸运的是,函数式编程已经被阐述的像你我这样的一般人也能理解和使用。函数式编程最主要的技术之一就是回调函数,你很快会阅读到,实现回调函数就像传递一般的参数变量一样简单。这项技术如此的简单,以至于我都怀疑为什么它经常被包含在JavaScript的高级话题中去。
什么是回调或高级函数?
回调函数被认为是一种高级函数,一种被作为参数传递给另一个函数(在这称作"otherFunction")的高级函数,回调函数会在otherFunction内被调用(或执行)。回调函数的本质是一种模式(一种解决常见问题的模式),因此回调函数也被称为回调模式。
思考一下下面这种在jQuery中常用的回调函数:
//Note that the item in the click method's parameter is a function, not a variable.
//The item is a callback function
$("#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方法,作为forEach的参数。
到目前为止,我们传递了一个匿名的函数作为参数给另一个函数或方法。在看其它更复杂的回调函数之前,让我们理解一下回调的工作原理并实现一个自己的回调函数。
friends.forEach(function (eachName, index){
console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 4. Rick
});
我们再一次用同样的方式传递了一个匿名的函数(没有函数名的函数)给forEach方法,作为forEach的参数。
到目前为止,我们传递了一个匿名的函数作为参数给另一个函数或方法。在看其它更复杂的回调函数之前,让我们理解一下回调的工作原理并实现一个自己的回调函数。
回调函数是如何实现的?
我们可以像使用变量一样使用函数,作为另一个函数的参数,在另一个函数中作为返回结果,在另一个函数中调用它。当我们作为参数传递一个回调函数给另一个函数时,我们只传递了这个函数的定义,并没有在参数中执行它。
当包含(调用)函数拥有了在参数中定义的回调函数后,它可以在任何时候调用(也就是回调)它。
这说明回调函数并不是立即执行,而是在包含函数的函数体内指定的位置“回调”它(形如其名)。所以,即使第一个jQuery的例子看起来是这样:
//The anonymous function is not being executed there in the parameter.
//The item is a callback function
$("#btn_1").click(function() {
alert("Btn 1 Clicked");
});
匿名函数将延迟在click函数的函数体内被调用,即使没有名称,也可以被包含函数通过 arguments对象访问。
回调函数是闭包的
当作为参数传递一个回调函数给另一个函数时,回调函数将在包含函数函数体内的某个位置被执行,就像回调函数在包含函数的函数体内定义一样。这意味着回调函数是闭包的,想更多地了解闭包(http://blog.csdn.net/luozhonghua2014/article/details/45585835) , 从所周知,闭包函数可以访问包含函数的作用域,所以,回调函数可以访问包含函数的变量,甚至是全局变量。
实现回调函数的基本原则
简单地说,自己实现回调函数的时候需要遵循几条原则。
使用命名函数或匿名函数作为回调
在前面的jQuery和forEach的例子中,我们在包含函数的参数中定义匿名函数,这是使用回调函数的通用形式之一,另一个经常被使用的形式是定义一个带名称的函数,并将函数名作为参数传递给另一个函数,例如:
// global variable
var allUserData = [];
// generic logStuff function that prints to console
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]);
}
}
}
// A function that takes two parameters, the last one a callback function
function getInput (options, callback) {
allUserData.push (options);
callback (options);
}
// When we call the getInput function, we pass logStuff as a parameter.
// So logStuff will be the function that will called back (or executed) inside the getInput function
getInput ({name:"Rich", speciality:"JavaScript"}, logStuff);
// name: Rich
// speciality: JavaScript
传递参数给回调函数
因为回调函数在执行的时候就和一般函数一样,我们可以传递参数给它。可以将包含函数的任何属性(或全局的属性)作为参数传递回调函数。在上一个例子中,我们将包含函数的options作为参数传递给回调函数。下面的例子让我们传递一个全局变量或本地变量给回调函数:
//Global variable
var generalLastName = "Clinton";
function getInput (options, callback) {
allUserData.push (options);
// Pass the global variable generalLastName to the callback function
callback (generalLastName, options);
}
在执行之前确保回调是一个函数
在调用之前,确保通过参数传递进来的回调是一个需要的函数通常是明智的。此外,让回调函数是可选的也是一个好的实践。
让我们重构一下上面例子中的getInput函数,确保回调函数做了适当的检查。
function getInput(options, callback) {
allUserData.push(options);
// Make sure the callback is a function
if (typeof callback === "function") {
// Call it, since we have confirmed it is callable
callback(options);
}
}
如果getInput函数没有做适当的检查(检查callback是否是函数,或是否通过参数传递进来了),我们的代码将会导致运行时错误。
使用含有this对象的回调函数的问题
当回调函数是一个含有this对象的方法时,我们必须修改执行回调函数的方法以保护this对象的内容。否则this对象将会指向全局的window对象(如果回调函数传递给了全局函数),或指向包含函数。让我们看看下面的代码:
// Define an object with some properties and a method
// We will later pass the method as a callback function to another function
var clientData = {
id: 094545,
fullName: "Not Set",
// setUserName is a method on the clientData object
setUserName: function (firstName, lastName) {
// this refers to the fullName property in this object
this.fullName = firstName + " " + lastName;
}
}
function getUserInput(firstName, lastName, callback) {
// Do other stuff to validate firstName/lastName here
// Now save the 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
// The fullName property was initialized on the window object
console.log (window.fullName); // Barack Obama
这样的设计是一个灾难(在继承模式中有论述)
使用Call或Apply函数保护this对象
我们可以通过使用 Call 或 Apply函数来解决前面示例中的问题。到目前为止,我们知道JavaScript中的每一个函数都有两个方法:Call和Apply。这些方法可以被用来在函数内部设置this对象的内容,并内容传递给函数参数指向的对象。
Call takes the value to be used as the this object inside the function as the first parameter, and the remaining arguments to be passed to the function are passed individually (separated by commas of course). The Apply function’s first parameter is also the value to be used as the thisobject inside the function, while the last parameter is an array of values (or the arguments object) to pass to the function. (这里主要说明call或apply如何传参(是对象或数组或任何),可以参考之前http://blog.csdn.net/luozhonghua2014/article/details/45585835)
这听起来很复杂,但让我们看看Apply和Call的使用是多么容易。为解决前面例子中出现的问题,我们使用Apply函数如下:
//Note that we have added an extra parameter for the callback object, called "callbackObj"
function getUserInput(firstName, lastName, callback, callbackObj) {
// Do other stuff to validate name here
// The use of the Apply function below will set the this object to be callbackObj
callback.apply (callbackObj, [firstName, lastName]);
}
通过Apply函数正确地设置this对象,现在我们可以正确地执行回调函数并它正确地设置clientData对象中的fullName属性。
// We pass the clientData.setUserName method and the clientData object as parameters. The clientData object will be used by the Apply function to set the this //object
getUserInput ("Barack", "Obama", clientData.setUserName, clientData);
// the fullName property on the clientData was correctly set
console.log (clientData.fullName); // Barack Obama
///////////一起执行一把////////////
// Define an object with some properties and a method
// We will later pass the method as a callback function to another function
var clientData = {
id: 094545,
fullName: "Not Set",
// setUserName is a method on the clientData object
setUserName: function (firstName, lastName) {
// this refers to the fullName property in this object
this.fullName = firstName + " " + lastName;
}
}
//Note that we have added an extra parameter for the callback object, called "callbackObj"
function getUserInput(firstName, lastName, callback, callbackObj) {
// Do other stuff to validate name here
// The use of the Apply function below will set the this object to be callbackObj
callback.apply (callbackObj, [firstName, lastName]);
}
// We pass the clientData.setUserName method and the clientData object as parameters. The clientData object will be used by the Apply function to set the this //object
getUserInput ("Barack", "Obama", clientData.setUserName, clientData);
// the fullName property on the clientData was correctly set
console.log (clientData.fullName); // Barack Obama
// The fullName property was initialized on the window object
console.log (window.fullName); // Barack Obama 这是为什么呢?这是window包含所有的属性,这个原理将在下次分析
// We will later pass the method as a callback function to another function
var clientData = {
id: 094545,
fullName: "Not Set",
// setUserName is a method on the clientData object
setUserName: function (firstName, lastName) {
// this refers to the fullName property in this object
this.fullName = firstName + " " + lastName;
}
}
//Note that we have added an extra parameter for the callback object, called "callbackObj"
function getUserInput(firstName, lastName, callback, callbackObj) {
// Do other stuff to validate name here
// The use of the Apply function below will set the this object to be callbackObj
callback.apply (callbackObj, [firstName, lastName]);
}
// We pass the clientData.setUserName method and the clientData object as parameters. The clientData object will be used by the Apply function to set the this //object
getUserInput ("Barack", "Obama", clientData.setUserName, clientData);
// the fullName property on the clientData was correctly set
console.log (clientData.fullName); // Barack Obama
// The fullName property was initialized on the window object
console.log (window.fullName); // Barack Obama 这是为什么呢?这是window包含所有的属性,这个原理将在下次分析
//////////////////////
我们也可以使用Call 函数,但在本例中我们使用的Apply 函数。
多重回调函数也是允许的
我们可以传递多个回调函数给另一个函数,就像传递多个变量一样。这是使用jQuery的AJAX函数的典型例子:
function successCallback() {
// Do stuff before send
}
function successCallback() {
// Do stuff if success message received
}
function completeCallback() {
// Do stuff upon completion
}
function errorCallback() {
// Do stuff if error received
}
$.ajax({
url:"http://fiddle.jshell.net/favicon.png",
success:successCallback,
complete:completeCallback,
error:errorCallback
});
多重回调函数也是允许的
我们可以传递多个回调函数给另一个函数,就像传递多个变量一样。这是使用jQuery的AJAX函数的典型例子:
function successCallback() {
// Do stuff before send
}
function successCallback() {
// Do stuff if success message received
}
function completeCallback() {
// Do stuff upon completion
}
function errorCallback() {
// Do stuff if error received
}
$.ajax({
url:"http://fiddle.jshell.net/favicon.png",
success:successCallback,
complete:completeCallback,
error:errorCallback
});
“回调地狱”的问题和解决方案
异步代码执行是一种简单的以任意顺序执行的方式,有时是很常见的有很多层级的回调函数,你看起来像下面这样的代码。下面这种凌乱的代码称作“回调地狱”,因为它是一种包含非常多的回调的麻烦的代码。我是在node-mongodb-native里看到这个例子的,MongoDB驱动Node.js.示例代码就像这样:
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();
});
});
});
});
});
});
你不太可能在自己的代码里碰到这个的问题,但如果你碰到了(或以后偶然碰到了),那么有以下两种方式解决这个问题。
命名并定义你的函数,然后传递函数名作为回调,而不是在主函数的参数列表里定义一个匿名函数。
模块化:把你的代码划分成一个个模块,这样你可以空出一部分代码块做特殊的工作。然后你可以将这个模型引入到你的大型应用程序中。
实现自己的回调函数
现在你已经完全理解(我相信你已经理解了,如果没有请快速重新阅读一遍)了JavaScript关于回调的所用特性并且看到回调的使用是如此简单但功能却很强大。你应该看看自己的代码是否有机会使用回调函数,有以下需求时你可以考虑使用回调:
避免重复代码 (DRY—Do Not Repeat Yourself)
在你需要更多的通用功能的地方更好地实现抽象(可处理各种类型的函数)。
增强代码的可维护性
增强代码的可读性
有更多定制的功能
实现自己的回调函数很简单,在下面的例子中,我可以创建一个函数完成所用的工作:获取用户数据,使用用户数据生成一首通用的诗,使用用户数据来欢迎用户,但这个函数将会是一个凌乱的函数,到处是if/else的判断,甚至会有很多的限制并无法执行应用程序可能需要的处理用户数据的其它函数。
替而代之的是我让实现增加了回调函数,这样主函数获取用户数据后可以传递用户全名和性别给回调函数的参数并执行回调函数以完成任何任务。
简而言之,getUserInput函数是通用的,它可以执行多个拥有各种功能的回调函数。
// First, setup the generic poem creator function; it will be the callback function in the getUserInput function below.
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");
}
//The callback, which is the last item in the parameter, will be our genericPoemMaker function we defined above.
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);
// Output
/* 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);
}
// Pass the greetUser function as a callback to getUserInput
getUserInput("Bill", "Gates", "Man", greetUser);
// And this is the output
Hello, Mr. Bill Gates
和上一个例子一样,我们调用了同一个getUserInput 函数,但这次却执行了完全不同的任务。
如你所见,回调函数提供了广泛的功能。尽管前面提到的例子非常简单,在你开始使用回调函数的时候思考一下你可以节省多少工作,如何更好地抽象你的代码。加油吧!在早上起来时想一想,在晚上睡觉前想一想,在你休息时想一想……
我们在JavaScript中经常使用回调函数时注意以下几点,尤其是现在的web应用开发,在第三方库和框架中
异步执行(例如读文件,发送HTTP请求)
事件监听和处理
设置超时和时间间隔的方法
通用化:代码简洁
我们在JavaScript中经常使用回调函数时注意以下几点,尤其是现在的web应用开发,在第三方库和框架中
异步执行(例如读文件,发送HTTP请求)
事件监听和处理
设置超时和时间间隔的方法
通用化:代码简洁
相关推荐
本文将深入探讨JavaScript回调函数的概念、工作原理以及如何在实际代码中应用。 回调函数本质上是一个在其他函数执行完毕后被调用的函数。在JavaScript中,由于其单线程特性,为了执行耗时任务(如读取文件或发送...
本篇文章将深入探讨jQuery中回调函数的原理及其应用。 回调函数本质上是一个在特定事件发生后被调用的函数,它允许我们将业务逻辑推迟到某个操作完成之后执行。在jQuery中,回调函数经常用于异步操作,如Ajax请求...
JavaScript回调函数是编程中一个重要的概念,尤其在异步编程中扮演着核心角色。本文将深入探讨回调函数的概念、作用、解释以及使用方法。 一、回调函数的作用 在JavaScript中,代码通常按照顺序执行。然而,有些...
### JavaScript回调函数中变量作用域解析 #### 一、引言 JavaScript作为一种广泛使用的脚本语言,其灵活性和强大功能使其成为Web开发不可或缺的一部分。在JavaScript编程中,回调函数是一种非常重要的概念,尤其是...
#### 二、取得回调函数返回值原理 在DWR中,当前端调用后端Java方法时,可以通过回调函数来接收返回的数据。回调函数是一种异步处理机制,它能够使前端在不阻塞的情况下等待后端处理完成,并获取结果。 #### 三、...
本文将从基本概念入手,逐步深入探讨回调函数的工作原理、应用场景以及优缺点,并通过具体的示例帮助读者更好地理解和运用回调函数。 ### 一、回调函数的基本概念 #### 1.1 定义 回调函数是指一个作为参数传递给另...
JavaScript中的callback回调函数是一种非常重要的概念,它允许在某个操作完成后由另一个函数来处理后续逻辑。回调函数可以是匿名函数、具名函数或是函数表达式,它作为参数传递给另一个函数,并在适当的时机被调用...
总的来说,回调函数是JavaScript编程中的基石,对于理解和编写高效、可维护的JavaScript代码至关重要。在实际开发中,理解并熟练运用回调函数可以极大地提高代码的灵活性和可扩展性。同时,掌握如何避免回调函数带来...
在编程中,回调函数是一种常见的设计模式,它...通过理解和实践JavaScript回调函数,可以有效地处理异步任务,提高程序的响应性和性能。掌握回调函数用法,对于深入学习JavaScript以及开发高性能Web应用来说至关重要。
随着编程语言和库的发展,回调函数的替代方案应运而生,如Promise、async/await等,它们旨在解决回调地狱和错误处理的问题,使异步编程更加优雅。 ### 结语 回调函数是异步编程的核心,理解其工作原理对于编写高效...
Java 回调函数是一种设计模式,它允许服务程序在适当的时候调用客户程序提供的功能。这种模式在异步编程、事件驱动编程以及分布式系统中尤为常见。回调函数的核心概念是客户程序提供一个函数给服务程序,然后服务...
9. **异步编程**:Promise和async/await使得处理异步操作更为直观,解决了回调地狱的问题。 10. **错误处理**:学习如何使用try...catch捕获和处理程序运行时可能出现的错误,以及如何使用throw语句自定义错误。 ...
本文将深入探讨jQuery `Ajax Post`回调函数不执行的原因,并提供相应的解决方法,同时也会分析`ajax success`回调函数不执行的情况。 首先,我们来看jQuery `$.post()`函数的基本用法,它通常用于发送POST类型的...
在JavaScript和AJAX编程中,回调函数是一种常见的方式,用于处理异步操作的结果。当一个AJAX请求完成时,我们通常会定义一个回调函数来处理返回的数据或执行其他相关操作。然而,有时我们需要向这些回调函数传递参数...
事件循环、回调函数、Promise和async/await等概念是处理异步操作的核心。通过源代码,你可以看到如何有效地编写异步代码以避免回调地狱。 5. **函数式编程**:JavaScript支持函数式编程范式,如高阶函数、函数组合...
让我们通过两个实例来深入理解回调函数的工作原理: **实例1** ```javascript // 定义主函数,接收回调函数作为参数 function A(callback) { callback(); console.log('我是主函数'); } // 定义回调函数 ...
在Vue.js应用中,开发者经常会遇到`this`指向问题,特别是在使用回调函数时。这个问题通常发生在异步操作中,如网络请求,因为这些操作会在不同的上下文中执行,导致`this`不再指向Vue实例。以下是对这个问题的深入...
5. **异步编程**:回调函数、Promise、async/await、事件循环与事件队列、定时器(setTimeout、setInterval)。 6. **DOM操作**:选择元素(getElementById、querySelectorAll等)、遍历DOM树、事件处理、修改元素...
在这篇"jQuery学习笔记之回调函数"中,我们将深入理解回调函数的工作原理及其在jQuery中的应用。 1. 回调函数定义: 回调函数是一种特殊的函数,它作为参数传递给其他函数,并在特定条件满足或特定事件触发时由该...