ES2015のArray.prototype.entries()を多重ループ内で使うケースがあったのですが、
entries()
を書く場所によって意図せぬ挙動になってハマったのでメモ。
例えばこんなコードがあったとします。
(この場合entries()
は使う必要ないんですが、説明のためなのでツッコミなしで)
const func = (arr) => {
for (let i = 0; i < 2; i++) {
for (const val of arr.entries()) {
console.log(`${i}, [${val}]`);
}
}
};
const arr = [1, 2, 3];
func(arr);
0, [0,1]
0, [1,2]
0, [2,3]
1, [0,1]
1, [1,2]
1, [2,3]
これを、関数呼び出しの時点でentries()
使うように書き換えると、外側ループが1回しか回らなくなっちゃいました!
const func = (arr) => {
for (let i = 0; i < 2; i++) {
for (const val of arr) {
console.log(`${i}, [${val}]`);
}
}
};
const arr = [1, 2, 3];
func(arr.entries());
0, [0,1]
0, [1,2]
0, [2,3]
理由ですが、正確には「外側ループが1回しか回っていない」のではなく、「2回目は内側ループが回らない」ということなのす。
entries()
を外側ループより前に書いた場合、「作成されたイテレータが回り切って元に戻らない」のでした。
試しにデバッグ文を追加すると、こんな出力になります。
const func = (arr) => {
for (let i = 0; i < 2; i++) {
console.log(`外側ループ ${i}`);
for (const val of arr) {
console.log(`${i}, [${val}]`);
}
}
console.log('ループ終了');
};
const arr = [1, 2, 3];
func(arr.entries());
外側ループ 0
0, [0,1]
0, [1,2]
0, [2,3]
外側ループ 1
ループ終了
なので、関数の中にentries()
書いても↓じゃダメ。
const func = (arr) => {
const itr = arr.entries();
for (let i = 0; i < 2; i++) {
for (const val of itr) {
console.log(`${i}, [${val}]`);
}
}
};
const arr = [1, 2, 3];
func(arr);
↓ならOKですね。
外側ループが1周する毎にentries()が呼ばれてイテレータが再作成されるので。
const func = (arr) => {
for (let i = 0; i < 2; i++) {
const itr = arr.entries();
for (const val of itr) {
console.log(`${i}, [${val}]`);
}
}
};
const arr = [1, 2, 3];
func(arr);
たぶんイテレータの扱いとしては基本的なことなんでしょうけど、
初心者は「何で変数の宣言場所違うと挙動変わるんじゃー(#゚Д゚)!」ってなりそうなので、
記事に残しておきました。
ではまた〜。