我最近通过阅读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中,变量的范围在它定义的函数内部以及所有嵌套函数中。但我很难在这里理解它。那么有人可以解释这里发生了什么吗? 谢谢
答案 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范围内,通过向下移动一系列对象来完成解析,这些对象定义了范围内的变量"对于那个代码。我们来看看怎么样?
因此,执行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]);
}
});
}