理解javascript中的回调函数(callback)【转】

在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,能够和其它String、Array、Number、Object类的对象同样用于内置对象的管理。由于function其实是一种对象,它能够“存储在变量中,经过参数传递给(别一个)函数(function),在函数内部建立,从函数中返回结果值”。
由于function是内置对象,咱们能够将它做为参数传递给另外一个函数,延迟到函数中执行,甚至执行后将它返回。这是在JavaScript中使用回调函数的精髓。本篇文章的剩余部分将全面学习JavaScript的回调函数。回调函数也许是JavaScript中使用最普遍的功能性编程技术,也许仅仅一小段JavaScript或jQuery的代码都会给开发者留下一种神秘感,阅读这篇文章后,也许会帮你消除这种神秘感。

 

回调函数来自一种著名的编程范式——函数式编程,在基本层面上,函数式编程指定的了函数的参数。函数式编程虽然如今的使用范围变小了,但它一直被“专业的聪明的”程序员看做是一种难懂的技术,之前是这样,将来也将是如此。javascript

幸运的是,函数式编程已经被阐述的像你我这样的通常人也能理解和使用。函数式编程最主要的技术之一就是回调函数,你很快会阅读到,实现回调函数就像传递通常的参数变量同样简单。这项技术如此的简单,以致于我都怀疑为何它常常被包含在JavaScript的高级话题中去。java

什么是回调或高级函数?

回调函数被认为是一种高级函数,一种被做为参数传递给另外一个函数(在这称做"otherFunction")的高级函数,回调函数会在otherFunction内被调用(或执行)。回调函数的本质是一种模式(一种解决常见问题的模式),所以回调函数也被称为回调模式。node

思考一下下面这种在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中。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的参数。web

到目前为止,咱们传递了一个匿名的函数做为参数给另外一个函数或方法。在看其它更复杂的回调函数以前,让咱们理解一下回调的工做原理并实现一个本身的回调函数。ajax

回调函数是如何实现的?

咱们能够像使用变量同样使用函数,做为另外一个函数的参数,在另外一个函数中做为返回结果,在另外一个函数中调用它。当咱们做为参数传递一个回调函数给另外一个函数时,咱们只传递了这个函数的定义,并无在参数中执行它。mongodb

当包含(调用)函数拥有了在参数中定义的回调函数后,它能够在任什么时候候调用(也就是回调)它。shell

这说明回调函数并非当即执行,而是在包含函数的函数体内指定的位置“回调”它(形如其名)。因此,即便第一个jQuery的例子看起来是这样:express

//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对象访问。

回调函数是闭包的
看成为参数传递一个回调函数给另外一个函数时,回调函数将在包含函数函数体内的某个位置被执行,就像回调函数在包含函数的函数体内定义同样。这意味着回调函数是闭包的,想更多地了解闭包,请参考做者另外一个贴子Understand JavaScript Closures With Ease。从所周知,闭包函数能够访问包含函数的做用域,因此,回调函数能够访问包含函数的变量,甚至是全局变量。

实现回调函数的基本原则

简单地说,本身实现回调函数的时候须要遵循几条原则。

使用命名函数或匿名函数做为回调
在前面的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.  (该段翻译起来太拗口了,放原文本身体会)

这听起来很复杂,但让咱们看看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

咱们也可使用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

});

“回调地狱”的问题和解决方案

异步代码执行是一种简单的以任意顺序执行的方式,有时是很常见的有不少层级的回调函数,你看起来像下面这样的代码。下面这种凌乱的代码称做“回调地狱”,由于它是一种包含很是多的回调的麻烦的代码。我是在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();
                    });
                });
            });
        });
    });
});

你不太可能在本身的代码里碰到这个的问题,但若是你碰到了(或之后偶然碰到了),那么有如下两种方式解决这个问题。

  1. 命名并定义你的函数,而后传递函数名做为回调,而不是在主函数的参数列表里定义一个匿名函数。
  2. 模块化:把你的代码划分红一个个模块,这样你能够空出一部分代码块作特殊的工做。而后你能够将这个模型引入到你的大型应用程序中。

    



实现本身的回调函数

如今你已经彻底理解(我相信你已经理解了,若是没有请快速从新阅读一遍)了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中的回调函数(callback),本文着重于对回调函数概念的理解,须要的朋友能够参考下

 

最近在看 express,满眼看去,处处是以函数做为参数的回调函数的使用。若是这个概念理解不了,nodejs、express 的代码就会看得一塌糊涂。好比:

复制代码代码以下:

app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

app是对象,use是方法,方法的参数是一个带参的匿名函数,函数体直接在后面给出了。这段代码怎么理解呢?咱们先来了解回调函数这个概念。
首先要了解,在 js 中,函数也是对象,能够赋值给变量,能够做为参数放在函数的参数列表中。好比:
复制代码代码以下:

var doSomething = function(a,b)
{
 return a + b;
}

这段代码的意思是定义一个匿名函数,这个匿名函数除了没有名字以外,其余跟普通的函数没有什么两样。而后把匿名函数赋值给变量doSomething。接下来咱们调用:
复制代码代码以下:

console.log(doSomething(2,3));

 

这样会输出5。

回调函数,就是放在另一个函数(如 parent)的参数列表中,做为参数传递给这个 parent,而后在 parent 函数体的某个位置执行。说来抽象,看例子:

复制代码代码以下:

// To illustrate the concept of callback
var doit = function(callback)
{
    var a = 1,
        b = 2,
        c = 3;
    var t = callback(a,b,c);
    return t + 10;
};
var d = doit(function(x,y,z){
    return (x+y+z);
});
console.log(d);

先定义 doit 函数,有一个参数 callback。这个 callback 就是回调函数,名字能够任意取。看函数体,先定义三个变量 a,b,c。而后调用 callback 函数。最后返回一个值。

 

下面就调用 doit 函数了。要注意的是,刚才定义 doit 时,callback 并无定义,因此刚才并不知道 callback 是干什么用的。这其实很好理解,咱们平时定义函数的时候,参数也只是给出了一个名字,好比 a,在函数体中使用 a,但整个过程也并不知道 a 究竟是什么,只有在调用那个函数的时候才指定 a 的具体值,好比2.回过头来,在调用 doit 的时候,咱们就须要指定 callback 到底是个什么东西了。能够看到,这个函数完成了一个 sum 功能。

上述代码的执行过程是:

调用 doit函数,参数是一个匿名函数;进入 doit 的函数体中,先定义 a,b,c,而后执行刚才的匿名函数,参数是 a,b,c,并返回一个 t,最后返回一个 t+10给 d。

回到最初的例子,app.use(...)是函数调用。咱们能够想象,以前必定定义了一个 use 方法,只是这里没有给出。这两个例子一对比,就能够立刻理解了。

在使用nodejs、express 的时候,不可能每一个方法或函数咱们都要找到它的函数定义去看一看。因此只要知道那个定义里面给 callback 传递了什么参数就好了。而后在调用方法或函数时,在参数里咱们本身定义匿名函数来完成某些功能。

Over!

 

 


Javascript中的Callback方法浅析

投稿:junjie 字体:[增长 减少] 类型:转载 时间:2015-03-15 我要评论

这篇文章主要介绍了Javascript中的Callback方法浅析,本文讲解了什么是callback、Javscript Callback、Callback是什么、Callback实例等内容,须要的朋友能够参考下
 

什么是callback


 回调函数就是一个经过函数指针调用的函数。若是你把函数的指针(地址)做为参数传递给另外一个函数,当这个指针被用为调用它所指向的函数时,咱们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

 

这个解释看上去很复杂,因而找到了知乎上一个更好的解释

 


 你到一个商店买东西,恰好你要的东西没有货,因而你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,而后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫作触发了回调关联的事件,店员给你打电话叫作调用回调函数,你到店里去取货叫作响应回调事件。回答完毕。

 

在Javascript中:

 


 函数A做为参数(函数引用)传递到另外一个函数B中,而且这个函数B执行函数A。咱们就说函数A叫作回调函数。若是没有名称(函数表达式),就叫作匿名回调函数。
实际上,也就是把函数做为参数传递。

 

Javscript Callback

把上面那些复杂的解释都丢到垃圾桶里吧~,看看Callback是什么

Callback是什么

在jQuery中, hide的方法大概是这样子的


$(selector).hide(speed,callback)



$('#element').hide(1000, function() {
    // callback function
});

咱们只须要在里面写一个简单的函数
复制代码代码以下:

$('#element').hide(1000, function() {
    console.log('Hide');
});

有一个小小的注释在这其中:Callback 函数在当前动画 100% 完成以后执行。而后咱们就能够看到真正的现象,当id为element的元素隐藏后,会在console中输出Hide。

 

就也就意味着:

Callback其实是,当一个函数执行完后,现执行的那个函数就是所谓的callback函数。

Callback做用

正常状况下函数都是按顺序执行的,然而Javascript是一个事件驱动的语言。


function hello(){
    console.log('hello');
}

 

function world(){
    console.log('world');
}

hello();
world();


因此正常状况下都会按顺序执行的,然而当执行world事件的时间比较长时。

function hello(){
    setTimeout( function(){
        console.log( 'hello' );
    }, 1000 );
}

 

function world(){
    console.log('world');
}

hello();
world();


那么这个时候就不是这样的,这时会输出world,再输出hello,故而咱们须要callback。

 

Callback实例

一个简单地例子以下

定义:
function add_callback(p1, p2 ,callback) {
    var my_number = p1 + p2;
    callback(my_number);
}

 调用:

add_callback(5, 15, function(num){    console.log("call " + num);});

在例子中咱们有一个add_callback的函数,接收三个参数:前两个是要相加的两个参数,第三个参数是回调函数。当函数执行时,返回相加结果,并在控制台中输出'call 20'。
相关文章
相关标签/搜索