通过node.js理解javascriptcallback的概念,特别是在循环中
我刚刚开始与node.js。 我已经做了一些Ajax的东西,但没有太复杂,所以callback仍然是我的头。 我看着asynchronous,但我需要的是顺序运行一些function。
我基本上有一些东西,从一个API拉一些JSON,创build一个新的,然后做了什么。 显然,我不能只运行它,因为它一次运行一切,并有一个空的JSON。 大多数进程必须按顺序运行,但是如果从API中提取JSON,则可以在等待的情况下提取其他JSON,这很好。 把callback放在循环中时我感到困惑。 我该如何处理索引? 我想我已经看到一些在循环内部使用callback的地方作为一种recursion函数,根本不使用for循环。
简单的例子会帮助很多。
如果callback是在同一个范围内定义的,则循环定义在(通常是这种情况),那么callback将有权访问索引variables。 暂且抛开NodeJS的细节,让我们考虑这个function:
function doSomething(callback) { callback(); }
该函数接受一个callback函数引用,它所做的就是调用它。 不是很令人兴奋。 🙂
现在让我们在一个循环中使用它:
var index; for (index = 0; index < 3; ++index) { doSomething(function() { console.log("index = " + index); }); }
(在计算密集型代码中 – 就像服务器进程一样 – 最好不要在生产代码中按字面做上述操作,我们稍后会回到这一点。)
现在,当我们运行它,我们看到预期的输出:
index = 0 index = 1 index = 2
我们的callback函数能够访问index
,因为callback函数是定义范围内的数据的一个闭包 。 (不要担心术语“closures”, closures并不复杂 。)
我之所以说最好不要在计算密集型生产代码中完成上述工作,是因为代码在每次迭代中都创build了一个函数(除了编译器中的花式优化之外,V8是非常聪明的,但是优化创build这些函数是非平凡)。 所以这是一个稍微修改过的例子:
var index; for (index = 0; index < 3; ++index) { doSomething(doSomethingCallback); } function doSomethingCallback() { console.log("index = " + index); }
这可能看起来有点令人惊讶,但它仍然以相同的方式工作,并且仍然具有相同的输出,因为doSomethingCallback
仍然是一个关于index
的闭包,所以它仍然可以看到index
的值。 但现在只有一个doSomethingCallback
函数,而不是每个循环中的新鲜函数。
现在我们来看一个负面的例子,这是行不通的:
foo(); function foo() { var index; for (index = 0; index < 3; ++index) { doSomething(myCallback); } } function myCallback() { console.log("index = " + index); // <== Error }
这会失败,因为myCallback
没有在相同的范围(或嵌套范围)中定义该index
是在中定义的,所以index
在myCallback
是未定义的。
最后,让我们考虑在循环中设置事件处理程序,因为必须小心。 这里我们将深入到NodeJS中:
var spawn = require('child_process').spawn; var commands = [ {cmd: 'ls', args: ['-lh', '/etc' ]}, {cmd: 'ls', args: ['-lh', '/usr' ]}, {cmd: 'ls', args: ['-lh', '/home']} ]; var index, command, child; for (index = 0; index < commands.length; ++index) { command = commands[index]; child = spawn(command.cmd, command.args); child.on('exit', function() { console.log("Process index " + index + " exited"); // <== WRONG }); }
看起来上面应该和我们以前的循环一样工作,但是有一个关键的区别。 在我们之前的循环中,callback被立即调用,所以它看到了正确的index
值,因为index
还没有机会继续前进。 但是,在上面,我们将在调用callback之前旋转循环。 结果? 我们看
Process index 3 exited Process index 3 exited Process index 3 exited
这是一个关键点。 闭包没有closures的数据副本 ,它有一个实时引用 。 所以,当每个进程的exit
callback被运行的时候,循环已经完成,所有三个调用都会看到相同的index
值(它是循环结尾的值)。
我们可以通过callback使用一个不会改变的variables来解决这个问题,就像这样:
var spawn = require('child_process').spawn; var commands = [ {cmd: 'ls', args: ['-lh', '/etc' ]}, {cmd: 'ls', args: ['-lh', '/usr' ]}, {cmd: 'ls', args: ['-lh', '/home']} ]; var index, command, child; for (index = 0; index < commands.length; ++index) { command = commands[index]; child = spawn(command.cmd, command.args); child.on('exit', makeExitCallback(index)); } function makeExitCallback(i) { return function() { console.log("Process index " + i + " exited"); }; }
现在我们输出正确的值(以任何顺序退出):
Process index 1 exited Process index 2 exited Process index 0 exited
工作的方式是,我们分配给exit
事件的callback在我们对makeExitCallback
的调用中closures了i
参数。 makeExitCallback
创build和返回的第一个callbackclosures了对该makeExitCallback
调用的i
值,它创build的第二个callbackclosures了对该makeExitCallback
调用的i
值(这与先前调用的i
值不同),等等。
如果你把上面的文章连上一篇文章 ,应该更清楚一些东西。 文章中的术语有点过时(ECMAScript 5使用更新的术语),但概念没有改变。