回调函数中为何无法引用上层函数的变量?
发布于 5 年前 作者 chapgaga 3518 次浏览 最后一次编辑是 3 年前 来自 问答
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?

14 回复

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] }) );

异步,闭包

@hingsir 闭包上层context里面的变量,不能被闭包直接使用么? 其他语言好像是行的

@chapgaga

JavaSript可以使用调用这个闭包函数时的Context中的变量。否则,闭包中引用了path[i],如果path === undefined,会报错的。

你的示例代码中出现的这种现象,归根结底是因为把循环和异步调用函数结合在一起使用。由于fs.exists()是一个异步函数,所以它只会在真正被调用时再去查看Context中的变量取值。此时,循环已经执行完毕,变量的取值分别是i = 3; path[3] = undefined;。因此,返回一个path not existed就是理所当然。

  1. 验证i = 3
// 验证循环与异步函数一起使用时的context

var fs = require('fs');

var paths=['c:/', 'd:/', ''];

for (var i = 0; i < paths.length; i++) {
	if (paths[i].length > 0) {
				
		fs.exists(paths[i], function(exists) {
			if (exists) {
				console.info('get path inside', paths[i], i);
				
			} else {
				console.error('path not exists', i);
			}
		});
	}
}
  1. 解决方法:使用Array.map()取代for循环
var fs = require('fs');

var paths=['c:/', 'd:/', ''];

paths.map(function(dest) {
	fs.exists(dest, function(exists) {
		console.log(dest, exists);
	});
});
  1. 如果目标对象不是一个数组,没法使用Array.map(),可以用Async等模块控制调用异步函数的流程。

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库

你遇到了经典错误!

@cwallow caolan, async作者名字看起来像是个中国人,呵呵

@hpgt 没错的

@cwallow 回调关系多的话,要用好多bind啊,这就是node让人头疼的地方吧?

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

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

@fish 额,我的帖子好像被人编辑过了:)

回到顶部