【JavaScript】NodeListなどの「配列っぽい」オブジェクトを本物の配列に変換する方法

目次

概要

document.querySelectorAll で取得した要素リスト(NodeList)や、関数の引数オブジェクト(arguments)、文字列などは「配列のようにインデックスでアクセスできるが、filtermap などの配列メソッドを持っていない」という特徴があります(これを類似配列オブジェクトと呼びます)。

これらをスプレッド構文 [...] を使って純粋な配列(Array)に変換することで、JavaScriptの強力な配列操作メソッドをフル活用できるようになります。本記事では、Webページ上の要素リストを取得し、特定の条件でフィルタリングする出席管理システムを例に解説します。

仕様(入出力)

スプレッド構文による配列化

構文意味戻り値
[...オブジェクト]イテラブル(反復可能)なオブジェクトを展開し、新しい配列の中に要素として格納する。Array (本物の配列)
  • 入力: NodeList, HTMLCollection, String (文字列), arguments など
  • 出力: 入力された要素を持つ標準的な配列 (Array)
  • メリット: filter, map, reduce などの配列専用メソッドが使えるようになる。

基本の使い方

1. NodeList を配列にする

DOM操作で最も頻出するパターンです。querySelectorAll の戻り値は配列ではないため、そのままでは filter できません。

// NodeListを取得(配列ではない)
const divList = document.querySelectorAll('div');

// 配列に変換
const divArray = [...divList];

// これで配列メソッドが使える
const activeDivs = divArray.filter(div => div.classList.contains('active'));

2. 文字列を配列にする

文字列を1文字ずつの配列に分解します。

const text = "Hello";
const chars = [...text];

console.log(chars); // ["H", "e", "l", "l", "o"]

コード全文(HTML / JavaScript)

会議の出席状況を管理する画面において、HTML上の要素リストから「出席(attendクラス付き)」のメンバーだけを抽出し、その名前を一覧表示するプログラムです。

HTML

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Attendance Checker</title>
    <style>
        .panel {
            font-family: 'Hiragino Kaku Gothic ProN', sans-serif;
            border: 1px solid #ccc;
            padding: 20px;
            max-width: 400px;
            background-color: #f9f9f9;
        }
        .member-card {
            padding: 10px;
            margin: 5px 0;
            border: 1px solid #ddd;
            background-color: white;
            border-radius: 4px;
            color: #555;
        }
        /* 出席者のスタイル */
        .member-card.attend {
            border-left: 5px solid #4CAF50;
            font-weight: bold;
            color: #2e7d32;
        }
        /* 欠席者のスタイル */
        .member-card.absent {
            border-left: 5px solid #ccc;
            background-color: #eee;
        }
        
        .btn-check {
            margin-top: 15px;
            padding: 10px 20px;
            background-color: #2196F3;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1rem;
        }
        .btn-check:hover { background-color: #1976D2; }

        .result-box {
            margin-top: 15px;
            padding: 10px;
            background-color: #e3f2fd;
            border-radius: 4px;
            min-height: 1.5em;
        }
    </style>
</head>
<body>

<div class="panel">
    <h3>定例会議 メンバーリスト</h3>
    
    <div id="member-container">
        <div class="member-card attend">森 (Mori)</div>
        <div class="member-card absent">小森 (Komori)</div>
        <div class="member-card attend">中森 (Nakamori)</div>
        <div class="member-card attend">大森 (Omori)</div>
        <div class="member-card absent">林 (Hayashi)</div>
    </div>

    <button id="btn-extract" class="btn-check">出席者を抽出 (filter)</button>
    
    <div id="output-area" class="result-box">
        ここに結果が表示されます
    </div>
</div>

<script src="converter.js"></script>
</body>
</html>

JavaScript

/**
 * 出席者抽出スクリプト
 * NodeListを配列に変換してfilterメソッドを使用する
 */

// DOM要素
const extractButton = document.getElementById('btn-extract');
const outputArea = document.getElementById('output-area');

/**
 * 出席者を抽出して表示する処理
 */
const extractAttendees = () => {
    // 1. NodeListを取得 (これは配列ではないためfilterが使えない)
    const allMembersNodeList = document.querySelectorAll('.member-card');

    // 2. スプレッド構文を使って本物の配列に変換
    const allMembersArray = [...allMembersNodeList];

    // 3. 配列メソッド filter を使用
    // クラス名 'attend' を含む要素だけを抽出
    const attendingMembers = allMembersArray.filter((element) => {
        return element.classList.contains('attend');
    });

    // 4. さらに map を使ってテキスト(名前)だけを取り出す
    const memberNames = attendingMembers.map((element) => {
        // 名前だけ抽出して整形
        return element.textContent.trim();
    });

    // 結果の表示
    // 配列の要素数と、カンマ区切りの名前を表示
    if (memberNames.length > 0) {
        outputArea.textContent = `出席 ${memberNames.length}名: ${memberNames.join(', ')}`;
    } else {
        outputArea.textContent = '出席者はいません。';
    }
};

// イベントリスナー
extractButton.addEventListener('click', extractAttendees);

カスタムポイント

  • Array.from() の利用:[...list] と同様に Array.from(list) でも配列化できます。Array.from(list, element => element.textContent) のように、第2引数にマップ関数を渡せる点が Array.from のメリットです。
  • 特定の要素だけ除外:filter の条件を !element.classList.contains('absent') に変更すれば、特定の除外フラグが付いていない要素だけを取得するロジックになります。

注意点

  1. IE (Internet Explorer) の非対応スプレッド構文 [...] はIEでは動作しません。レガシーブラウザに対応する必要がある場合は、Array.prototype.slice.call(nodeList) という古い書き方を使用するか、Babelなどのトランスパイラを使用してください。
  2. パフォーマンス要素数が数万件単位で非常に多い場合、一度配列に変換してコピーを作成する処理はメモリと時間を消費します。単純なループ処理であれば、無理に配列化せず for 文や for...of 文で直接 NodeList を回す方が高速な場合があります。

応用

関数の引数(arguments)を配列化する

関数内で可変長引数を扱う際、arguments オブジェクトを配列化すると操作しやすくなります。

(※現在は「残余引数構文 ...args」の使用が推奨されますが、古いコードを読む際に役立ちます)

function sumAll() {
    // argumentsオブジェクトを配列化
    const args = [...arguments];
    
    // reduceメソッドで合計を計算
    return args.reduce((sum, num) => sum + num, 0);
}

console.log(sumAll(10, 20, 30)); // 60

まとめ

Web開発において「querySelectorAll で取得した要素を filtermap したい」という要求は日常的に発生します。

  • 取得: document.querySelectorAll(...)NodeList
  • 変換: [...NodeList]Array
  • 操作: Array.filter(...)

この一連の流れ(イディオム)を習得することで、DOM操作の効率が格段に向上します。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次