2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Array(3).fill([]) は「3つの空配列」ではない — 2次元配列が全行そろって書き換わる誤解と直し方

2
Posted at

「3行×3列のマス目を作って、左上だけ 1 にしたい」というコードを fill で書くと、1列目が全部 1 になります。Array(3).fill([]) で作った3つの配列の1つに push すると、3つとも同じ中身になります。

どちらも原因は同じで、fill に配列やオブジェクトを渡すと、同じものが3か所に入ることです。この記事では、よくある誤解を Q&A の形で3つ取り上げ、正しい作り方まで実測で確認します。実行環境は Node.js v24.14.1 です(コメントの出力は見やすく1行に整形しています)。

誤解1:Array(3).fill([]) で「別々の空配列が3つ」できる

const a = Array(3).fill([]);
a[0].push("りんご");
console.log(a);            // [["りんご"],["りんご"],["りんご"]]
console.log(a[0] === a[1]); // true

fill は、渡された値そのものを全部の位置に入れます。数値や文字列ならそれで問題ありませんが、配列やオブジェクトは「中身」ではなく「同じ1つの配列(オブジェクト)への参照」が3つ並ぶことになります。a[0] === a[1] が true なのはそのためです。

オブジェクトでも同じです。

const todos = Array(2).fill({ done: false });
todos[0].done = true;
console.log(todos); // [{"done":true},{"done":true}]

数値で埋める場合は問題ありません。数値や文字列は中身をその場で書き換えられないので、z[0] = 5 のように位置ごと入れ替えるしかなく、ほかの位置には影響しないからです。

const z = Array(3).fill(0);
z[0] = 5;
console.log(z); // [5,0,0]

配列で困るのは、push や grid[0][0] = 1 のように共有している中身を書き換えたときです。配列でも a[0] = ["みかん"] と位置ごと入れ替えれば、ほかの位置には影響しません。

誤解2:2次元配列は Array(3).fill(Array(3).fill(0)) で作れる

const grid = Array(3).fill(Array(3).fill(0));
grid[0][0] = 1;
console.log(grid); // [[1,0,0],[1,0,0],[1,0,0]]

内側の Array(3).fill(0) は1回しか実行されません。できあがった1本の行が、外側の3か所すべてに入ります。なので grid[0][0] を書き換えると、全行の左端が 1 になります。

マス目のゲームや座席表、カレンダーの表などで2次元配列を作るときに起きやすく、「1マスだけ塗ったつもりが1列全部塗られる」という見た目で現れます。

誤解3:それなら Array(3).map(() => []) で作ればいい

const b = Array(3).map(() => []);
console.log(b);        // [ <3 empty items> ]
console.log(b.length); // 3

Array(3) は長さだけが 3 の、中身が空っぽ(empty)の配列です。map は空っぽの位置を飛ばすので、コールバックが1回も呼ばれず、空のままになります。

空っぽの位置を map が飛ばす挙動そのものは、delete で配列に穴が開く話として以前の記事で書きました。ここでは「Array(n) は作った直後から全部が穴」という点だけ押さえておきます。

console.log(0 in Array(3)); // false(0番目の要素そのものが存在しない)

正しい作り方:要素ごとに新しく作る

要素ごとに関数を呼んで、毎回新しい配列を作らせます。

const c = Array.from({ length: 3 }, () => []);
c[0].push("りんご");
console.log(c);            // [["りんご"],[],[]]
console.log(c[0] === c[1]); // false

const grid2 = Array.from({ length: 3 }, () => Array(3).fill(0));
grid2[0][0] = 1;
console.log(grid2); // [[1,0,0],[0,0,0],[0,0,0]]

Array.from の2つ目の引数は、要素ごとに呼ばれる関数です。中で [] や Array(3).fill(0) を書けば、呼ばれるたびに新しい配列ができます。内側の Array(3).fill(0) は数値 0 で埋めているだけなので、ここは fill で問題ありません。

ほかにも、空っぽの位置をいったん値で埋めてから map する書き方でも同じ結果になります。TypeScript では fill の引数を省略できないので、fill(null) と書いておくと JavaScript と TypeScript のどちらでも使えます。

const d = Array(3).fill(null).map(() => []);   // [[],[],[]]
const e = [...Array(3)].map(() => []);         // [[],[],[]]

まとめ:fill に渡していいもの

渡すもの fill で埋めた結果
数値・文字列・true/false・null ✅ それぞれ独立した値として使える
配列 []・オブジェクト {} ❌ 同じ1つが全部の位置に入る
Array(n).fill(...) の結果(2次元配列の行) ❌ 同じ1本の行が全部の位置に入る

配列やオブジェクトで埋めたいときは Array.from({ length: n }, () => 新しく作る式) を使う、と覚えておくと迷いにくくなります。

AI が書いた初期化コードで見るところ

Claude Code などの AI にマス目や表のコードを頼むと、Array(n).fill(...) で2次元配列を作るコードが出てくることもあります。レビューするときは、fill( に渡しているものが配列やオブジェクトになっていないかを見てください([・{・Array(・new ○○() のほか、それらを入れた変数も含みます)。const row = Array(3).fill(0); Array(3).fill(row) のように変数を挟んでも、全行が同じ row を共有します。当てはまったら、「各行が同じ配列を共有していないか、1マスだけ書き換えて確かめてください」と頼むと、AI 自身に確認させられます。


未経験者向けの講座を運営しています

未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。

※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?