如何避免 map 函数中的变量范围问题?

How to avoid variable scope issues within a map function?

我有这个问题的有效解决方案,但我正在尝试尽可能地制作一个更干净整洁的版本。我想出了另一种解决方案,它在 map 函数中使用了一个函数。不幸的是,这个版本有一些问题,我只想知道为什么第二个解决方案不起作用。我猜这是一个可变范围的问题。我很期待知道您对此的看法。

我有一个简单的函数,可以在数组中打印日历日!

所以问题是为什么我的代码的第一个版本得到了预期的结果,而第二个版本却打印出意外的结果。

我试图将 let 更改为 var 并且我还使 counterstartedIndexing 超出了函数范围。

解决方案 1(有效):

const currentFullMonth = {
   days_length: 31,
   first_day: "Thu",
   first_day_index: 4,
   last_day: "Sat",
   last_day_index: 6,
   month: "Aug",
   year: 2019
}

const testMonth = [
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0]
];

function printMonthCalender(month) {
    let counter = 0;
    let startedIdxing = false;
    return month.map(week => {
        return week.map((day, index) => {
            if (index === currentFullMonth.first_day_index && !startedIdxing) {
                counter++;
                startedIdxing = true;
                return counter;
            } else if (startedIdxing) {
                if (currentFullMonth.days_length === counter) {
                    counter = 0;
                }
                counter++;
                return counter;
            } else {
                return 0;
            }
        });
    });
} // end of Solution #1 <-- this works :)

解决方案 2(无效):

// start of Solution #2 <-- does not work :(    
// im using two functions to make it look more cleaner
//
function printMonthCalender2(month) {
    let counter = 0;
    let startedIdxing = false;
    return month.map(week => {
        return week.map((day, index) =>
            indexingMonth(counter, startedIdxing, index)
        );
    });
}
function indexingMonth(counter, startedIdxing, index) {
    if (index === currentFullMonth.first_day_index && !startedIdxing) {
        counter++;
        startedIdxing = true;
        return counter;
    } else if (startedIdxing) {
        if (currentFullMonth.days_length === counter) {
            counter = 0;
        }
        counter++;
        return counter;
    } else {
        return 0;
    }
}// end of Solution #2

console.log(printMonthCalender(testMonth));
console.log(printMonthCalender2(testMonth));

预期结果如下(第一版):

[0, 0, 0, 0, 1, 2, 3]
[4, 5, 6, 7, 8, 9, 10]
[11, 12, 13, 14, 15, 16, 17]
[18, 19, 20, 21, 22, 23, 24]
[25, 26, 27, 28, 29, 30, 31]
[1, 2, 3, 4, 5, 6, 7]

意外结果如下(第二版):

[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]

问题是,当您在 indexingMonth 内部重新分配 startedIdxing 时,它是一个局部变量,因此它不会在调用函数 (printMonthCalender2) 内部发生更改。

一个问题是 .map 不应将突变或重新分配作为副作用。虽然您 可以 调整一些东西,以便 indexingMonth 返回您检查过的东西,然后将 startedIdxing 重新分配给,但我更喜欢不同的方法:创建一个平面数组,例如

[0, 0, 0, 0, 1, 2, ..., 30, 31, 1, 2, 3]

然后将其分成 7 块:

const currentFullMonth = {
   days_length: 31,
   first_day: "Thu",
   first_day_index: 4,
   last_day: "Sat",
   last_day_index: 6,
   month: "Aug",
   year: 2019
}

const makeZeroArr = length => new Array(length).fill(0);
const printMonthCalendar = (testMonth) => {
  // Create array: [1, 2, 3, ..., 30, 31]
  const oneMonth = Array.from(
    { length: currentFullMonth.days_length },
    (_, i) => i + 1
  );
  // Create a flat array with leading zeros and trailing last week:
  // [0, 0, 0, 0, 1, 2, 3, ..., 30, 31, 1, 2, 3, 4, 5, 6, 7]
  const flatResultArr = [
    ...makeZeroArr(currentFullMonth.first_day_index),
    ...oneMonth,
    ...oneMonth // this includes extra numbers that will be trimmed
  ].slice(0, 7 * 6); // 7 days/week * 6 weeks
  // Chunk the flat array into slices of 7:
  const resultArr = [];
  for (let i = 0; i < 7; i++) {
    resultArr.push(flatResultArr.slice(i * 7, (i + 1) * 7));
  }
  return resultArr;
};

console.log(printMonthCalendar());

在函数中,像数字和布尔值这样的基本类型是按值传递的,而不是按引用传递的。所以当你在[中定义counterstartedIdxing时=13=],然后尝试在 indexingMonth 中更改它们,一旦您 return 更改为 printMonthCalender2,更改就会丢失。

但是在 JavaScript 中,对象通过引用传递。所以这样的事情会起作用:

function printMonthCalender2(month) {
  let obj = { counter: 0, startedIdxing = false };
  return month.map(week => {
    return week.map((day, index) =>
      indexingMonth(obj, index)
    );
  });
}
function indexingMonth(obj, index) {
  if (index === currentFullMonth.first_day_index && !obj.startedIdxing) {
    obj.counter++;
    obj.startedIdxing = true;
    return obj.counter;
  } else if (obj.startedIdxing) {
    if (currentFullMonth.days_length === obj.counter) {
      obj.counter = 0;
    }
    obj.counter++;
    return obj.counter;
  } else {
    return 0;
  }
}// end of Solution #2

obj.counter++ 这样的东西实际上会将这些更改保留在 printMonthCalender2 中定义的原始对象中。


警告:虽然您可以这样做,但如果您正在处理复杂的代码,这通常是不受欢迎的。如果出现问题,这些类型的突变可能很难调试。这是一种合法的编程技术,但不应被滥用。

此外,如果您在一个坚持函数式编程范式的团队中工作,我认为这是一个很大的禁忌。

然而,鉴于本例中 obj 变量的持续时间非常短且范围有限,我个人对此感到非常满意。如果 obj 具有更长的生命周期并且在代码中的许多地方使用,那么我会更加谨慎,并且同意@CertainPerformance 的评论,即 map 语句不应该改变事物.