为什么Node.js在这两个用途中显示出不同的参数?

看看这个超级简单的node.js程序:

var g = { a : 1, b : 2 } function callBack (key, value) { console.log("Callback called with key: " + key + "\nAnd value: " + value) ; } function doNothing (key, value, cb) { true ; console.log(key + ": doing nothing") ; cb() ; } function doLoop () { for (k in g) { f = function () { callBack(k, g[k]) ; } doNothing(k, g[k], f) ; } } doLoop() ; 

运行时,它会产生这个输出:

 a: doing nothing Callback called with key: a And value: 1 b: doing nothing Callback called with key: b And value: 2 

好。 这是有道理的 – 每次调用callback,它都有正确的参数。

现在看看这个程序:

 var mysql = require('mysql') ; var dbClient = undefined ; var db_uri = "mysql://xxx:xxx@127.0.0.1/xxx" ; var schema = { redirects : "(id int AUTO_INCREMENT, key VARCHAR(50), url VARCHAR(2048))", clicks : "(ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, IP VARBINARY(16))" } ; function createOnEmpty(err, results, fields, tableName, create_def) { console.log("createOnEmpty called on " + tableName) ; if (err) { console.error(err) ; process.exit(1) ; } else { if (0 == results.length) { dbClient.query(["create table ", tableName, create_def].join(" "), function (err, results, fields) {} ) ; } else { console.log(tableName + " table already exists.") ; } } console.log("\n\n") ; } function setupSchema() { for (table in schema) { console.log("Checking for table: " + table) ; // FIXME: Why does this always seem to pass clicks as tablename?! dbClient.query( "show tables LIKE '" + table + "'", function (err, results, fields) { createOnEmpty(err, results, fields, table, schema[table]) } ); } } function handleDBConnect(err) { if (err) { console.error("ERROR: problem connecting to DB: " + err.code) ; process.exit(1) ; } else { console.log("Connected to database.") ; // Automatically set up the schema, if the tables don't exist setupSchema() ; } } function MySQLConnect() { dbClient = mysql.createConnection(db_uri) ; dbClient.connect(handleDBConnect) ; } MySQLConnect() ; 

它输出:

 Connected to database. Checking for table: redirects Checking for table: clicks createOnEmpty called on clicks createOnEmpty called on clicks 

尽pipevariables已经清楚地被切换到“redirect”,循环似乎也将参数'clicks'作为参数'table'给出。

我想我必须对JavaScript / Node在这里的工作有一些基本的误解。

要理解这个行为,你需要理解这个2个核心的js概念:

  • closures
  • 事件循环

假设我们有全局variablesa ,输出到控制台的log函数,以及调用两次log主函数,第一次有超时(asynchronous),第二次只是简单的函数调用

 var a = 42; function log() { console.log(`a is ${a}`); } function main() { setTimeout(log, 100); a = 13; log(); } main(); 

该代码产生以下输出:

 a is 13 a is 13 

为什么第一次是13?

当你调用setTimeout时,它不会阻塞主js线程100ms,它只是将日志函数添加到callback队列中。 下一行是a = 13 。 因为a没有在var关键字的函数体内声明,13分配给a在第一行代码声明。 然后我们有第一行输出作为main函数的最后一行的结果。 现在我们有空的调用堆栈,在我们的代码中没有其他事情发生,但是我们在callback队列中仍然有日志function。 经过100ms后,当且仅当callstack为空(这是我们的情况)时,可以再次调用logfunction。 它再次logging'a is 13' ,因为-s值已被重新分配。

这是关于asynchronouscallback如何在JavaScript中工作的一个简短的解释,这就是为什么createOnEmpty called on clicks两次createOnEmpty called on clicks的原因。 dbClient.query是asynchronous的,当它第一次被调用的时候,你的for循环完成它的执行, table值是clicks 。 快速和肮脏的解决你的问题将是

 for (table in schema) { console.log("Checking for table: " + table) ; (function (table) { dbClient.query("show tables LIKE '" + table + "'", function (err, results, fields) { createOnEmpty(err, results, fields, table, schema[table]) } ); )(table); } 

这个即时调用函数在每个循环迭代中记忆table

  • 看看这个关于eventloop的优秀讨论以及如何在js中使用asynchronous的东西
  • 如何closures工作

在调用dbClient.query的callbackdbClient.query ,循环已经完成,将(隐式全局) tablevariables留在schema的最后一个键上。

您需要使用匿名函数 (请参阅各种答案)或使用基于callback的迭代,如下所示:

 function setupSchema() { Object.keys(schema).forEach(function (table) { console.log("Checking for table: " + table) ; // FIXME: Why does this always seem to pass clicks as tablename?! dbClient.query( "show tables LIKE '" + table + "'", function (err, results, fields) { createOnEmpty(err, results, fields, table, schema[table]) } ); }); }