for 循环引用出错的问题在 Nodejs 也存在?

for 循环引用出错的问题在 Nodejs 也存在?

想写一个递归创建目录的脚本, 开始发现创建目录都不对的… 先是一个模块:

events = require "events"
exports.ring = ring = new events.EventEmitter

create_file = (base_path, data) ->
  if (typeof data) is "string"
    file_path = path.join base_path, data
    fs.writeFile file_path, ""
  else
    for key, value of data
      new_path = path.join base_path, key
      fs.mkdir new_path, ->
        create_file new_path, value

ring.on "create", (tree, base_path) ->
  base_path = base_path or "."
  create_file base_path, tree

然后在另一个文件引用:

file_tree =
  "parent": "child"
  "tree":
    "a": "aa"
    "b": "bbb"

scaffold = require("../lib/index").ring

scaffold.emit "create", file_tree

创建的目录结构是这样的:

➤➤ tree
.
|-- create.coffee
|-- parent
`-- tree
    |-- a
    `-- b
        `-- bbb

create.coffee 是我跑脚本的文件, 文件树几个子文件没有创建成功…

然后我把 for in 循环改成了 Object.keysforEach, 文件创建成功, 以前在 DOM 节点绑定操作的时候遇到过这个问题, 为什么文件操作里也有? 记得原来的说法是循环当中没有创建作用域, 可传值在 JS 里也出错的么?


6 回复

帖子内容:for 循环引用出错的问题在 Nodejs 也存在?

概述

在编写递归创建目录的脚本时,遇到了一些问题。起初,创建的目录结构并不正确。本文将探讨这个问题,并展示如何通过修改循环方式来解决。

初始代码

首先,我们有一个模块用于递归创建目录:

const events = require("events");
const path = require("path");
const fs = require("fs");

class DirectoryCreator extends events.EventEmitter {
  constructor() {
    super();
    this.create_file = this.create_file.bind(this);
  }

  create_file(base_path, data) {
    if (typeof data === "string") {
      const file_path = path.join(base_path, data);
      fs.writeFile(file_path, "");
    } else {
      for (let key in data) {
        const new_path = path.join(base_path, key);
        fs.mkdir(new_path, () => {
          this.create_file(new_path, data[key]);
        });
      }
    }
  }
}

module.exports = DirectoryCreator;

引用模块

然后,在另一个文件中引用并使用该模块:

const DirectoryCreator = require("./DirectoryCreator");

const file_tree = {
  "parent": "child",
  "tree": {
    "a": "aa",
    "b": "bbb"
  }
};

const creator = new DirectoryCreator();
creator.on("create", (tree, base_path) => {
  base_path = base_path || ".";
  creator.create_file(base_path, tree);
});

creator.emit("create", file_tree);

问题描述

当运行脚本时,发现创建的目录结构不正确。例如,预期的目录结构是:

.
|-- parent
|   `-- child
`-- tree
    |-- a
    |   `-- aa
    `-- b
        `-- bbb

但实际上创建的目录结构是:

.
|-- parent
`-- tree
    |-- a
    `-- b
        `-- bbb

解决方案

经过分析,问题出在 for...in 循环中。在 fs.mkdir 回调函数中,由于异步操作,key 的值可能已经改变。因此,我们可以通过使用 Object.keysArray.prototype.forEach 来解决这个问题。

修改后的代码如下:

create_file(base_path, data) {
  if (typeof data === "string") {
    const file_path = path.join(base_path, data);
    fs.writeFile(file_path, "");
  } else {
    Object.keys(data).forEach(key => {
      const new_path = path.join(base_path, key);
      fs.mkdir(new_path, () => {
        this.create_file(new_path, data[key]);
      });
    });
  }
}

结论

在 Node.js 中,for...in 循环在处理异步操作时可能会导致意外的结果。通过使用 Object.keysArray.prototype.forEach,我们可以确保每个异步操作都能正确地使用当前的 key 值,从而避免这种问题。


  new_path = path.join base_path, key
  fs.mkdir new_path, ->
    create_file new_path, value

为了让所有人看明白,转换成正常的javascript语法

new_path=path.join(base_path,key);
fs.mkdir(new_path,function(err){
    create_file(new_path,value);
});

第二行的new_path确实是传值给fs.mkdir函数,但是第三行的new_path却不是在fs,mkdir的作用域内,而是在闭包回调函数的作用域内,它是指向第一行的new_path的。

模拟测试代码

var tree={a:'a',b:'b'};
var keys=Object.keys(tree);
for(var i=0;i<keys.length;i++){
    var path='/root/'+keys[i];
    create_path(path,function(err){
        console.log('from callback - %s',path);
    });
}
function create_path(path,callback){
    process.nextTick(function(){
        console.log('from function - %s',path);
        callback();
    });
}

这么说完全是异步的操作都会出这样的问题咯… 而且 DOM 和 fs 的操作全都是异步的, 这样的确好理解多了 那么以后对于这样的情况都应该用闭包处理一遍了 … 怎么会这样的… 不能传值

非不能,只是闭包函数没有明确定义接受值的参数,不怕麻烦可以bind一个参数进去,例如这样就能达到传值的目的。

var tree={a:'a',b:'b'};
var keys=Object.keys(tree);
for(var i=0;i<keys.length;i++){
    var path='/root/'+keys[i];
    create_path(path,function(path,err){
        console.log('from callback - %s',path);
    }.bind(undefined,path));
}
function create_path(path,callback){
    process.nextTick(function(){
        console.log('from function - %s',path);
        callback();
    });
}

原来这样也可以, 感觉和闭包类似啊

在Node.js中,for...in循环确实可能引起一些问题,特别是在处理异步操作时。这是因为for...in循环中的异步操作可能会在循环完成之前执行完毕,导致变量的值在循环内部和外部不一致。

下面是一个示例代码,展示了使用for...in循环和使用Object.keysforEach的区别:

使用 for...in 循环

const fs = require('fs');
const path = require('path');

const createFile = (basePath, data) => {
  if (typeof data === 'string') {
    const filePath = path.join(basePath, data);
    fs.writeFile(filePath, "", (err) => {
      if (err) console.error(err);
    });
  } else {
    for (let key in data) {
      const newPath = path.join(basePath, key);
      fs.mkdir(newPath, () => {
        createFile(newPath, data[key]);
      });
    }
  }
};

createFile(".", { 
  "parent": "child",
  "tree": {
    "a": "aa",
    "b": "bbb"
  }
});

使用 Object.keysforEach

const fs = require('fs');
const path = require('path');

const createFile = (basePath, data) => {
  if (typeof data === 'string') {
    const filePath = path.join(basePath, data);
    fs.writeFile(filePath, "", (err) => {
      if (err) console.error(err);
    });
  } else {
    Object.keys(data).forEach(key => {
      const newPath = path.join(basePath, key);
      fs.mkdir(newPath, () => {
        createFile(newPath, data[key]);
      });
    });
  }
};

createFile(".", { 
  "parent": "child",
  "tree": {
    "a": "aa",
    "b": "bbb"
  }
});

在这两个例子中,第一个使用了for...in循环,而第二个使用了Object.keysforEach。后者避免了由于循环变量作用域带来的问题,并且能确保每个异步操作正确执行。

解释

  • for...in 循环:在异步操作(如fs.mkdir)中使用for...in循环时,循环变量可能会被覆盖,导致后续的操作无法正确执行。
  • Object.keysforEach:通过将对象的键转换为数组并使用forEach遍历,可以确保每个异步操作都能独立执行,避免作用域问题。

总之,在处理异步操作时,建议使用Object.keysforEach来代替for...in循环,以避免潜在的作用域问题。

回到顶部