如何避免 map 函数中的变量范围问题?
How to avoid variable scope issues within a map function?
我有这个问题的有效解决方案,但我正在尝试尽可能地制作一个更干净整洁的版本。我想出了另一种解决方案,它在 map 函数中使用了一个函数。不幸的是,这个版本有一些问题,我只想知道为什么第二个解决方案不起作用。我猜这是一个可变范围的问题。我很期待知道您对此的看法。
我有一个简单的函数,可以在数组中打印日历日!
所以问题是为什么我的代码的第一个版本得到了预期的结果,而第二个版本却打印出意外的结果。
我试图将 let
更改为 var
并且我还使 counter
和 startedIndexing
超出了函数范围。
解决方案 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());
在函数中,像数字和布尔值这样的基本类型是按值传递的,而不是按引用传递的。所以当你在[中定义counter
和startedIdxing
时=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
语句不应该改变事物.
我有这个问题的有效解决方案,但我正在尝试尽可能地制作一个更干净整洁的版本。我想出了另一种解决方案,它在 map 函数中使用了一个函数。不幸的是,这个版本有一些问题,我只想知道为什么第二个解决方案不起作用。我猜这是一个可变范围的问题。我很期待知道您对此的看法。
我有一个简单的函数,可以在数组中打印日历日!
所以问题是为什么我的代码的第一个版本得到了预期的结果,而第二个版本却打印出意外的结果。
我试图将 let
更改为 var
并且我还使 counter
和 startedIndexing
超出了函数范围。
解决方案 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());
在函数中,像数字和布尔值这样的基本类型是按值传递的,而不是按引用传递的。所以当你在[中定义counter
和startedIdxing
时=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
语句不应该改变事物.