Nodejs回调函数中为何无法引用上层函数的变量?

Nodejs回调函数中为何无法引用上层函数的变量?

var fs=require(‘fs’);
var path=[‘c:/’, ‘d:/’, ‘’];
for (var i = 0; i < path.length; i++) {
if (path[i].length > 0) {
// 可以得到值
console.info(path[i]);
fs.exists(path[i], function(exists) {
if (exists) {
// 为何得到的是undefined?如何将下面的变量正确输出呢?
console.info(‘get path inside’, path[i]);
} else {
console.error(‘path not exists’);
}
})

} }

输出怎么是undefined?


10 回复

在Node.js中,当你在一个外部作用域中定义了一个变量,并且试图在回调函数中引用它时,可能会遇到一些意料之外的行为。这是因为JavaScript的作用域链和闭包机制导致的。

让我们来看一下你的代码:

var fs = require('fs');
var path = ['c:/', 'd:/', ''];

for (var i = 0; i < path.length; i++) {
  if (path[i].length > 0) {
    // 这里可以正确输出路径
    console.info(path[i]);

    fs.exists(path[i], function(exists) {
      if (exists) {
        // 为什么这里会输出undefined?如何正确地输出路径?
        console.info('get path inside', path[i]);
      } else {
        console.error('path not exists');
      }
    });
  }
}

当你运行这段代码时,你可能会发现console.info('get path inside', path[i]);打印出的是undefined,而不是预期的路径。

原因

这是因为fs.exists的回调函数是在未来的某个时刻被调用的,而此时循环已经结束,i的值为path.length(即3)。因此,当回调函数执行时,path[i]实际上已经变成了path[3],而path[3]并不存在,所以它的值为undefined

解决方法

为了正确地捕获每个循环中的path[i]值,你可以使用闭包来创建一个新的作用域。例如,可以通过立即执行函数表达式(IIFE)来创建一个新作用域:

var fs = require('fs');
var path = ['c:/', 'd:/', ''];

for (var i = 0; i < path.length; i++) {
  if (path[i].length > 0) {
    // 正确输出路径
    console.info(path[i]);

    (function(currentPath) {
      fs.exists(currentPath, function(exists) {
        if (exists) {
          console.info('get path inside', currentPath);
        } else {
          console.error('path not exists');
        }
      });
    })(path[i]);
  }
}

在这个例子中,我们通过一个IIFE将当前路径currentPath作为参数传递给回调函数,从而确保每次回调函数执行时都能访问到正确的路径值。

通过这种方式,你可以避免在回调函数中出现undefined的情况,确保能够正确地引用上层函数中的变量。


bind 进去就好了

fs.exists(path[i], function(exists) {
  if (exists) {
    console.info('get path inside’, this.path);
  } else {
    console.error(‘path not exists’);
  }
}.bind({ path:path[i] }) );

异步,闭包

var path=['c:/’, 'd:/’, ‘’]; 我怎么觉得你这标点符号是错的

很明显这里形成了一个闭包,由于回调函数是异步的,在执行回调函数时其所在的上层作用域已经执行完退出了,但是由于闭包的原因,回调函数仍然引用了上层作用域 的变量对象,但此时变量的值已经改变,在这里就是i=3,所以输出undefined,此时我们可以在循环内部开辟一个执行作用域,把包含回调函数的方法放入这个作用域中,,这样就进行了作用域隔离,可以避免这个问题,因此可以这样修改:

var fs=require('fs');
var path=['c:/', 'd:/', ''];
for (var i = 0; i < path.length; i++) {
	if (path[i].length > 0) {
		// 可以得到值
		console.info(path[i]);
		(function(i){
			fs.exists(path[i], function(exists) {
				if (exists) {
				console.info('get path inside', path[i]);
				} else {
				console.error('path not exists');
				}
			});
		})(i);
	}
}

内部是异步的,等你内部使用时,外部循环已经结束了(即i=3了),这时访问肯定是undefiend,转换成函数或使用async库

你遇到了经典错误!

4楼给出了说明 … 6楼已经给出了答案 . 结贴吧.

强迫症了, LZ 看一下现在帖子的内容, 插入代码 用 ```

在Node.js中,回调函数内的作用域链会访问到外层函数的变量,但是由于JavaScript的作用域和闭包机制,当回调函数被异步调用时,外层函数可能已经执行完毕,此时循环变量 i 的值可能已经变化,导致回调函数内部获取到的变量值不是预期的值。

具体来说,在你的例子中,fs.exists 回调函数中的 path[i] 实际上是在循环结束后才被执行,此时 i 的值已经是循环结束后的值,因此所有回调函数中 path[i] 都会指向最后一个元素,导致输出 undefined 或者最后一个路径。

示例代码

var fs = require('fs');
var path = ['c:/', 'd:/', ''];

for (var i = 0; i < path.length; i++) {
  if (path[i].length > 0) {
    console.info(path[i]);

    // 使用闭包来捕获当前的 i 值
    (function(i) {
      fs.exists(path[i], function(exists) {
        if (exists) {
          console.info('get path inside', path[i]);
        } else {
          console.error('path not exists');
        }
      });
    })(i); // 立即执行函数表达式来捕获当前的 i 值
  }
}

解释

在这个修改后的代码中,我们使用了一个立即执行函数表达式(IIFE)来创建一个新的作用域,并且在这个新的作用域中捕获了当前的 i 值。这样,每个回调函数都能正确地访问到对应的路径。

另一种更现代的方法是使用 let 关键字声明 i,这样每次循环迭代都会创建一个新的 i 变量绑定,从而避免上述问题:

for (let i = 0; i < path.length; i++) {
  if (path[i].length > 0) {
    console.info(path[i]);
    fs.exists(path[i], function(exists) {
      if (exists) {
        console.info('get path inside', path[i]);
      } else {
        console.error('path not exists');
      }
    });
  }
}

在这个版本中,每次迭代都会创建一个新的 i 变量,从而确保每个回调函数都能正确访问到正确的路径。

回到顶部