配列内のオブジェクトに変更を加えたのに反映されない
記事の概要
先日「ループ処理で配列内のオブジェクトに対して変更を加える」ような処理を書いていたのですが、配列内オブジェクトに変更を加えたはずなのになぜかその変更が反映されないという問題に遭遇し、原因を突き止めるのに時間がかかったので、調べたことを忘れないようにこの記事を書きました。
参照渡しについての理解が浅かったことが原因でして恐らく基本の内容かと思われますが、同問題で困っている方にはもしかしたら参考になるかもしれないです。(配列の参照渡し周りについてざっくり書いています)
問題と解決方法の概要(サンプルコードあり)
問題コード(処理1)
今回遭遇した問題を簡単にしたもので、リザードがレベル36以上の場合リザードンに進化させる処理を書いているのですが、実際に動かしてみるとリザードのままになります。
const party = [
{ name: "ヒトカゲ", level: 8 },
{ name: "リザード", level: 36 },
{ name: "リザードン", level: 100 },
];
// 処理1
for (let pokémon of party) {
if (pokémon.name === "リザード" && pokémon.level >= 36) {
pokémon = { name: "リザードン", level: pokémon.level };
}
}
console.log(party);
// [{ name: 'ヒトカゲ', level: 8 },{ name: 'リザード', level: 36 },{ name: 'リザードン', level: 100 }]
解決コード(処理2)
問題を解決したコードになります。
以下の書き方だと、無事リザードがリザードンに進化しました。
const party = [
{ name: "ヒトカゲ", level: 8 },
{ name: "リザード", level: 36 },
{ name: "リザードン", level: 100 },
];
// 処理2
for (const i in party) {
if (party[i].name === "リザード" && party[i].level >= 36) {
party[i] = { name: "リザードン", level: party[i].level };
}
}
console.log(party);
// [{ name: 'ヒトカゲ', level: 8 },{ name: 'リザードン', level: 36 },{ name: 'リザードン', level: 100 }]
解決コード(処理3・処理4)
少し調べたところ、処理2の書き方はあまりよくないらしく、もう少し良さそうな解決コードを2つ書いてみました。(参考:JavaScript の for ループ / for in ループ/ for of ループ ってなにが違うの?)
処理3は新しいオブジェクトを詰めなおしたい場合に使えそうかなと思います。
処理4は既存のオブジェクトの一部の値を変更したい場合に使えそうかなと思います。
const party = [
{ name: "ヒトカゲ", level: 8 },
{ name: "リザード", level: 36 },
{ name: "リザードン", level: 100 },
];
// 処理3
for (let i = 0; i < party.length; i++) {
if (party[i].name === "リザード" && party[i].level >= 36) {
party[i] = { name: "リザードン", level: party[i].level };
}
}
console.log(party);
// [{ name: 'ヒトカゲ', level: 8 },{ name: 'リザードン', level: 36 },{ name: 'リザードン', level: 100 }]
const party = [
{ name: "ヒトカゲ", level: 8 },
{ name: "リザード", level: 36 },
{ name: "リザードン", level: 100 },
];
// 処理4
for (const pokémon of party) {
if (pokémon.name === "リザード" && pokémon.level >= 36) {
pokémon.name = "リザードン";
}
}
console.log(party);
問題と解決方法の詳細(図解あり)
問題コード(処理1)
処理1はpokémon
という変数に、元々の配列
内に保存されている元々のオブジェクト
への参照を渡して処理を実行する動きになっているため、for文内の2回目のループ時の動きとしてはpokémon
に新しいオブジェクト
への参照を渡しているだけなので、元々の配列
には何の変化も起きないという結果になったようです。
解決コード(処理2・処理3)
処理2と処理3は元々の配列
内に保存されている元々のオブジェクト
への参照を新しいオブジェクト
への参照に変更する処理になっているので、元々の配列
に変化が起きるという結果になったようです。
解決コード(処理4)
処理4はpokémon
という変数に、元々の配列
内に保存されている元々のオブジェクト
への参照を渡して処理を実行する動きになっていますが、for文内の2回目のループ時の動きとしては参照先の元々のオブジェクト
のプロパティを変更する動きになっているので、元々の配列
に変化が起きるという結果になったようです。
まとめ
上記の内容を書く際に参考にさせていただいた記事が以下になります。
何かおかしな点に気づいた方や、「もっといい書き方あるよ!」という方はコメント等いただけると嬉しいです。
ここまでお読みいただきありがとうございました。
Discussion
こういったのはデータ構造にIDを持たせて、mapなどで処理するのがいいと思いました
発展させて、signiaとdiomaを組み合わせて、Reactでデモしてみました
リザードンからリザードへ退化
リザードからリザードンへ進化
の2つをレベル36をエッジとして、チャレンジしてみました
コメントありがとうございます!
発展形のデモもつけていただきありがとうございます!(せっかくデモしていただいたところ申し訳ないのですが、理解しきれなかったので勉強させてもらいます…!)
おっしゃるとおりmapを使う方が良いですね!
書いていただいたコードから読み取れず大変申し訳ないのですが、データ構造にIDを持たせるのは同名かつ同レベルのポケモンを識別できるようにするためでしょうか?
進化させる条件predicateとは別の観点ではあるのですが、大体合っていそうです
Web化ないしアプリ化するにあたり、UIを用意するとは思うのですが、ポケモンはレベルアップすると進化するので、どのポケモンをレベルアップさせるかを特定するためにも、IDは必要です
このIDを持ってクリックイベントでレベルアップ、レベルダウンさせています
また、退化は進化との対称性の観点から、実際にあるかは別として組み込んだだけとなります
情報設計の観点からもIDで体系付けて管理するというのもあります
たとえば、グルーピング処理等で、同名同レベルのカウント数を集約するといったことがある際はIDで体系付けてあることで、IDの数を持ってポケモンの集約情報の取得を達成できます
理解できました、ありがとうございます!
同名・同レベルのポケモンがパーティーに2体いた場合、任意で選んだ片方のポケモンのみ進化させるケースなども想定するとIDは必要ですね!
グルーピングの際に
ID
とレベル
でフィルタリングするという意味だと読み取ったのですが、その方法が名前
とレベル
でフィルタリングする方法よりも優れているのはどういった点になりますでしょうか?(フィルタリングの速度が速くなるなどでしょうか?)フィルタリングの文脈は登場させているつもりはないです
名前、レベル単位でグルーピングする場合は、IDで体系づけていることで、その件数でもって、確かめることができますよねといったぐらいの程度のものです。
すいません勘違いしてました。
サンプルコードも勉強になりました!色々と教えていただきありがとうございました!