Node.js在回调中调用回调函数

时间:2014-12-10 04:08:46

标签: javascript node.js callback

我最近通过阅读Action book中的Node JS开始学习node.js。这可能是一个新手问题,但在阅读了几个回调函数和javascript变量范围之后,我仍然有问题在本书第5章中理解了这段代码背后的想法。

function loadOrInitializeTaskArray(file, cb) {
  fs.exists(file, function(exists) {
    var tasks = [];
    if (exists) {
      fs.readFile(file, 'utf8', function(err, data) {
        if (err) throw err;
        var data = data.toString();
        var tasks = JSON.parse(data || '[]');
        cb(tasks);
      });
    } else {
      cb([]); 
    }
  });
}

function listTasks(file) {
  loadOrInitializeTaskArray(file, function(tasks) {
    for(var i in tasks) {
      console.log(tasks[i]);
    }
  });
}

它包括三个回调函数,分为两个函数。首先调用listTasks(..),稍后调用loadorInitializeTaskArray(..)。我的问题从这里开始,这个调用如何由节点处理? loadOrInitializeTaskArray有两个参数,第二个是回调函数,它不应该根据签名接受任何参数但它确实!!
何时在loadorInitializeTaskArray中调用cb(..)以及它是什么(调用辅助函数的函数相同)?

"任务"是一个在函数loadOrInitializeTaskArray中声明的数组,我们如何在listTasks(..)函数中访问它?

我知道在Javascript中,变量的范围在它定义的函数内部以及所有嵌套函数中。但我很难在这里理解它。那么有人可以解释这里发生了什么吗? 谢谢

3 个答案:

答案 0 :(得分:3)

你真的很难理解嵌套函数中变量的范围。所以,让我们从它开始吧 我们来考虑一下这段代码

function foo(){
  var a = 3;
  function bar(){
    console.log(a);
  }
  return bar;
}
var baz = foo();
baz(); // the value of a i.e 3 will appear on console

如果你知道像C,C ++那样的语言......你的大脑会像这样解释这段代码。如果你不知道他们中的任何一个就忽略了这些点。

  • 调用foo()时,将声明变量a并为其指定值3.
  • 返回foo时,包含a的堆栈将被销毁。因此也会被摧毁。
  • 然后世界baz()如何输出3。 ???

好吧,在调用函数的javascript中,发生的事情与C中的不同。所以,在进一步阅读之前,先让你的所有C事情从你的脑海中消失。

在javascript范围内,通过向下移动一系列对象来完成解析,这些对象定义了范围内的变量"对于那个代码。我们来看看怎么样?

  • 当您声明一个全局JavaScript变量时,您实际在做的是定义全局对象的属性。
  • 在顶级JavaScript代码(即未包含在任何函数定义中的代码)中,作用域链由单个对象(全局对象)组成。
  • 在非嵌套函数中,作用域链由两个对象组成。第一个是定义函数参数和局部变量的对象,第二个是全局对象。
  • 在嵌套函数中,作用域链具有三个或更多对象。定义函数时,它会存储范围链然后生效。调用该函数时,它会创建一个新对象来存储其局部变量,并将该新对象添加到存储的作用域链中,以创建一个新的,更长的链,表示该函数调用的作用域。

因此,执行foo()时。它创建了一个新对象来存储它的局部变量。因此,变量a将存储在该对象中。而且,bar是定义的。当定义bar时,它会存储范围链。因此,bar的作用域链现在包含其中包含变量a的对象。因此,当返回bar时,它会引用它的范围链。因此它知道a

所以,我猜它会回答你的问题节点如何处理代码。

您写道:

  

loadOrInitializeTaskArray有两个参数,第二个是回调函数,根据签名不应该接受任何参数,但确实如此!!

回调函数是

function(tasks) {
    for(var i in tasks) {
      console.log(tasks[i]);
    }
}

它接受一个参数tasks。所以,你错了。

当调用loadOrIntializeTaskArray时,cb指的是此回调函数。 cb(arg)基本上执行此操作tasks = arg,其中tasks是回调函数中的参数。

我猜你还会有很多问题。请在评论中告诉我。我强烈建议您在进入节点之前先阅读Core Javascript。

答案 1 :(得分:0)

我不确定你的意思是“第二个是回调函数,根据签名不应接受任何参数”。回调签名(function(tasks))当然确实需要一个参数(tasks)。

cb是传入的回调函数。在这种情况下,它实际上是匿名函数:

function(tasks) {
  for(var i in tasks) {
    console.log(tasks[i]);
  }
}

tasks是两个不同变量的名称(但它们指向相同的对象,因为数组通过引用传递)在不同的范围内。如您所述,其中一个在loadOrInitializeTaskArray()中定义,另一个是传递给loadOrInitializeTaskArray()的回调的参数。

此外,在不相关的注释中,通常回调遵循“错误优先”格式。这允许在上游处理错误。修改代码以遵循此约定将导致:

function loadOrInitializeTaskArray(file, cb) {
  fs.exists(file, function(exists) {
    var tasks = [];
    if (exists) {
      fs.readFile(file, 'utf8', function(err, data) {
        if (err)
          return cb(err);

        var data = data.toString();

        // JSON.parse() throws errors/exceptions on parse errors
        try {
          var tasks = JSON.parse(data || '[]');
        } catch (ex) {
          return cb(ex);
        }

        cb(null, tasks);
      });
    } else {
      cb(null, []); 
    }
  });
}

function listTasks(file) {
  loadOrInitializeTaskArray(file, function(err, tasks) {
    if (err) throw err;
    for (var i in tasks) {
      console.log(tasks[i]);
    }
  });
}

答案 2 :(得分:0)

首先,javascript中没有任何函数签名这样的东西。您可以根据需要向函数传递任意数量的参数。 loadOrInitialiseTaskArray的第二个参数在调用函数时简单地分配给局部变量cb。然后行cb(tasks)调用此值,因此第二个参数最好是函数。

当你从loadOrInitializeTaskArray调用listTasks时,第二个参数确实是一个函数,并且该函数的第一个参数在其自己的范围内被命名为tasks。它没有进入loadOrInitializeTaskArray并使用在该函数范围内声明的变量。您在调用cb时显式传入了该值。

如果我们重命名变量,代码将工作相同,也许更容易理解:

function loadOrInitializeTaskArray(file, cb) {
  fs.exists(file, function(exists) {
    var taskArray = [];
    if (exists) {
      fs.readFile(file, 'utf8', function(err, data) {
        if (err) throw err;
        var data = data.toString();
        var taskArray = JSON.parse(data || '[]');
        cb(taskArray);
      });
    } else {
      cb([]); 
    }
  });
}

function listTasks(file) {
  loadOrInitializeTaskArray(file, function(listOfTasks) {
    for(var i in listOfTasks) {
      console.log(listOfTasks[i]);
    }
  });
}