JavaScript基礎 #5 オブジェクトと配列

#4 関数 で動作をまとめる道具を見たので、今度は データをまとめる道具 の番です。JavaScriptの2つの中核データ構造 — オブジェクトと配列。

オブジェクト — キーと値の組の集まり #

オブジェクトの基本
const user = {
  id: 'u1',
  name: 'カーティス',
  age: 30,
  isAdmin: false,
};

console.log(user.name);     // カーティス
console.log(user['name']);  // カーティス — 同じ効果

オブジェクトは キーと値の組 をまとめるデータ構造です。キーは文字列(またはシンボル)、値はどんな型でも構いません。

プロパティの追加 / 修正 / 削除 #

オブジェクトの変更
const user = { id: 'u1', name: 'カーティス' };

user.age = 30;          // 追加
user.name = '別の名前';  // 修正
delete user.id;          // 削除

console.log(user);
// { name: '別の名前', age: 30 }

const で宣言したのにオブジェクトの中の値が変わるのは、最初は混乱します。const は変数が指している場所を変えられないという意味 であって、オブジェクトの中の値が変えられないという意味ではありません(#2 のプリミティブ vs 参照を参照)。

短縮表記 #

同じ名前の変数をオブジェクトのキーとして使うとき、短縮できます。

プロパティの短縮
const id = 'u1';
const name = 'カーティス';

// フル表記
const user = { id: id, name: name };

// 短縮 — 変数名とキー名が同じなら1回だけ
const userShort = { id, name };

ES2015以降では基本となる形なので、フル表記をしているのは古いコードくらいです。

メソッドの短縮 #

値として関数を置くときにも短縮があります。

メソッドの短縮
const calc = {
  add(a, b) { return a + b; },          // 短縮
  subtract: function(a, b) { return a - b; },  // フル表記
};

calc.add(2, 3);        // 5
calc.subtract(5, 2);   // 3

配列 — 順序のある値のまとまり #

配列の基本
const fruits = ['りんご', 'バナナ', 'ぶどう'];

console.log(fruits[0]);       // りんご — インデックスは0から
console.log(fruits.length);   // 3
console.log(fruits[10]);      // undefined — 存在しないインデックス

配列もオブジェクトの一種です(typeof []'object')。ただし、インデックスで順序のあるデータを扱いやすいように特化されています。

追加 / 削除 #

配列の変更
const fruits = ['りんご'];

fruits.push('バナナ');     // 末尾に追加
// ['りんご', 'バナナ']

fruits.unshift('ぶどう');    // 先頭に追加
// ['ぶどう', 'りんご', 'バナナ']

fruits.pop();              // 末尾から削除
// ['ぶどう', 'りんご']

fruits.shift();            // 先頭から削除
// ['りんご']

これら4つのメソッドは元の配列を直接変更します。元を変えずに新しい配列を作りたい場合は、spread(後ほど)を使うほうが安全です。

よく使う配列のメソッド — map, filter, reduce #

反復しながら変換 / 絞り込み / 集約する3つのメソッド。モダンJavaScriptで最もよく使う道具です。

map — 変換 #

map — すべての要素を変換
const nums = [1, 2, 3, 4, 5];

const doubled = nums.map((n) => n * 2);
// [2, 4, 6, 8, 10]

const labels = nums.map((n) => `#${n}`);
// ['#1', '#2', '#3', '#4', '#5']

元の配列はそのままにして、新しい配列 を返します。長さは同じで、各要素はコールバックの戻り値になります。

filter — 絞り込み #

filter — 条件に合うものだけ
const nums = [1, 2, 3, 4, 5];

const evens = nums.filter((n) => n % 2 === 0);
// [2, 4]

const big = nums.filter((n) => n > 3);
// [4, 5]

コールバックが truthy を返した要素だけが残ります。長さが減ることもあります。

reduce — 集約 #

reduce — ひとつの値に集約
const nums = [1, 2, 3, 4, 5];

const sum = nums.reduce((acc, n) => acc + n, 0);
// 15

const max = nums.reduce((acc, n) => n > acc ? n : acc, -Infinity);
// 5

第2引数(0-Infinity)は 初期値 です。これを起点に、コールバックを1要素ずつ適用しながら値を集約していきます。最も強力なメソッドですが、最初は流れが見えにくいかもしれません。map/filter だけでは解けない場面で取り出す道具です。

チェーン #

3つのメソッドは互いに繋げて使うのに向いています。

チェーン — 偶数だけ選んで2倍
const nums = [1, 2, 3, 4, 5];

const result = nums
  .filter((n) => n % 2 === 0)
  .map((n) => n * 2);
// [4, 8]

各メソッドが 新しい配列を返す ので、自然に繋がります。

よく使うその他のメソッド #

検索 / 検査
[1, 2, 3].includes(2);            // true
[1, 2, 3].indexOf(2);              // 1
[{id: 'a'}].find((x) => x.id === 'a');    // {id: 'a'}
[1, 2, 3].some((n) => n > 2);     // true (ひとつでも)
[1, 2, 3].every((n) => n > 0);    // true (すべて)
変形 — 新しい配列を返す
[1, 2, 3].slice(1, 3);            // [2, 3]
[1, 2, 3].concat([4, 5]);         // [1, 2, 3, 4, 5]
[3, 1, 2].toSorted();             // [1, 2, 3] (元の配列を保持)
文字列変換
[1, 2, 3].join(',');              // '1,2,3'
[1, 2, 3].join(' / ');            // '1 / 2 / 3'

toSorted はES2023で追加されたモダンなメソッドです。昔は sort が元の配列を変えてしまい事故が頻発していましたが、toSorted ならすっきりします。

Spread (...) — 展開 #

オブジェクトと配列をほどいて再びまとめる構文です。

配列 spread
const a = [1, 2, 3];
const b = [...a, 4, 5];          // [1, 2, 3, 4, 5]
const copy = [...a];              // [1, 2, 3] — 浅いコピー

const merged = [...a, ...b];      // 2つの配列を連結
オブジェクト spread
const user = { id: 'u1', name: 'カーティス' };
const updated = { ...user, age: 30 };
// { id: 'u1', name: 'カーティス', age: 30 }

const overridden = { ...user, name: '別の名前' };
// { id: 'u1', name: '別の名前' }   ← 後に書いたほうが勝つ

このパターンは、JavaScriptで仕事をしていて最もよく使う慣用句のひとつです。元を変えずに、変更後の新しい値を作る 場面に向いています。

分割代入 (Destructuring) — ばらして受け取る #

オブジェクトや配列の値を一度に複数の変数にばらして受け取る構文です。

オブジェクトの分割代入 #

オブジェクトの分割代入
const user = { id: 'u1', name: 'カーティス', age: 30 };

const { name, age } = user;
console.log(name);  // カーティス
console.log(age);   // 30

// 名前を変えて受け取る
const { name: userName } = user;
console.log(userName);   // カーティス

// デフォルト値
const { email = 'なし' } = user;
console.log(email);   // なし

関数の仮引数でもよく使います。React講座 のpropsの分割代入が、まさにこの構文です。

仮引数の分割代入
function greet({ name, age }) {
  console.log(`こんにちは、${name} (${age})`);
}

greet({ name: 'カーティス', age: 30 });

配列の分割代入 #

配列の分割代入
const [first, second, third] = [10, 20, 30];
console.log(first, second, third);   // 10 20 30

// スキップ
const [, , third] = [10, 20, 30];
console.log(third);   // 30

// rest で残り
const [head, ...tail] = [1, 2, 3, 4];
console.log(head);   // 1
console.log(tail);   // [2, 3, 4]

useStateconst [count, setCount] = useState(0) がまさにこの構文です。

オブジェクトのキーを動的に — 計算されたプロパティ #

キーそのものを変数にしたいとき。

計算されたプロパティ
const key = 'name';
const value = 'カーティス';

const user = {
  [key]: value,
  [`is${key}Required`]: true,
};
// { name: 'カーティス', isnameRequired: true }

角括弧 [...] の中に式を入れることができます。フォームハンドラで name 属性をキーとして使うパターンなど、実践でよく出てきます。

Object.keys / values / entries #

オブジェクトを配列のように扱いたいとき。

オブジェクトを配列に
const user = { id: 'u1', name: 'カーティス', age: 30 };

Object.keys(user);    // ['id', 'name', 'age']
Object.values(user);  // ['u1', 'カーティス', 30]
Object.entries(user); // [['id', 'u1'], ['name', 'カーティス'], ['age', 30]]

反復と組み合わせると強力です。

entriesでオブジェクトを反復
for (const [key, value] of Object.entries(user)) {
  console.log(`${key} = ${value}`);
}

まとめ #

今回の記事で整理した内容:

  • オブジェクト — キーと値の組、短縮表記とメソッド短縮
  • 配列 — 順序のあるまとまり、push/pop/shift/unshift は元を変える
  • map/filter/reduce — 変換 / 絞り込み / 集約 の3つのメソッド
  • some/every/find/includes のような検査メソッド
  • spread ... でオブジェクト / 配列を展開しシャローコピー
  • 分割代入 — オブジェクト / 配列をほどいて変数で受け取る
  • 計算されたプロパティ — { [key]: value }
  • Object.keys/values/entries でオブジェクトを配列のように扱う

次の記事(#6 文字列とテンプレートリテラル)では、文字列の扱い — よく使うメソッド、テンプレートリテラル、そして正規表現の基礎を扱います。

X