CodeGrid 12th Event Site

配列操作ドリル 前編 スプレッド構文を利用した配列操作の基本

当たり前のように使う構文でも、JavaScriptの進化とともに書き方が変わったり、書き方の選択肢が増えたりするものがあります。今回のテーマの配列もその一つ。ドリル形式で、その書き方を見直してみましょう。

発行

著者 中村 享介 Jamstackエンジニア
配列操作ドリル シリーズの記事一覧

はじめに

JavaScriptを書いていると当たり前のように使う配列ですが、JavaScriptの進化とともに、さまざまな書き方ができるようになっています。選択肢が多いため、どういうふうに書けばよいのか迷ってしまったり、同じプロジェクトなのに人によって書き方が変わってしまったりするといった問題もあります。

久しぶりのドリルシリーズの今回は、基本的な配列の操作についての問題を用意しました。

最近JavaScriptを学んでいる方だけでなく、昔からJavaScriptに慣れ親しんでいるベテランエンジニアにとっても、最近の書き方としてより良い方法へのアップデートのきっかけにしてもらえるとうれしいです。

今回の問題

用意した問題は5問、そのうち今回の記事で解説するのは3問目までになります。すぐできる簡単な問題ですので、ぜひご自身でも、問題を解いてから解説を読んでみてください。先に問題を試すと、よく理解できるし、また楽しめるように構成しています。

今回も、ピクセルグリッド社内のエンジニアや最近あまりコードを書いてないディレクターに解いてもらったので、その解答をもとに解説します。なお、社内では、出した問題はCodeSandboxのコードで共有し、それをフォークすることで解答できるようにしていました。その問題は以下にありますので、同様にCodeSandboxでフォークして解答することもできます。

第1問

配列aの最後にbを追加した新しい配列resultを作ってください。
ただし、配列aを変更してはいけません。

const a = [1, 2, 3];
const b = 4;

// コードを書いて result = [1, 2, 3, 4] にする

第2問

配列aの最後の要素を削除した新しい配列resultを作ってください。
削除した最後の要素はdeletedItemに入れてください。
ただし、配列aを変更してはいけません。

const a = [1, 2, 3, 4];

// deletedItem = 4, result = [1, 2, 3] にする

第3問

配列aとbをつなげた新しい配列resultを作ってください。
ただし、配列aもbも変更してはいけません。

const a = [1, 2, 3];
const b = [4, 5, 6];

// result = [1, 2, 3, 4, 5, 6] にする

第1問:配列の末尾に追加

それでは最初の問題です。

問題

配列aの最後にbを追加した新しい配列resultを作ってください。
ただし、配列aを変更してはいけません。

const a = [1, 2, 3];
const b = 4;

// コードを書いて result = [1, 2, 3, 4] にする

解答例

const result = [...a, b];

解説

社内の多くの解答が上記のスプレッド構文を使ったものです。筆者の想定もこれでした。

問題を読むと配列の末尾に追加なので、push()を使いたくなりますが、元の配列を変更してはいけないという条件なので、そのままpush()を使うわけにはいきません。スプレッド構文を使うと、配列の中身を展開できます。これを使って、新しい配列の最初にaの配列を展開、そして後ろにbをくっつけるというコードになっています。

なお、同様に配列の頭に追加するunshift()に関しても、同様にスプレッド構文で表現できます。bをaの配列の頭に追加するなら、以下のようになります。

const result = [b, ...a];

見た目からも、どういうことを行っているのかわかりやすく、スプレッド構文の利用はおすすめの方法です。

その他の解答例としてはconcat()を使ったものがあります。

const result = [].concat(a).concat(b);

解答では空の配列に順番に追加していく形で書かれていましたが、concat()はそもそも新しい配列を返すので、以下でもいいでしょう。

const result = a.concat(b);

ただし、先ほど解説したunshift()の例のように、頭に追加するとなると最初にくる要素が配列とは限りません。その場合に備えて、次の解答例のように空の配列から書くのも1つの方法です。

const result = [].concat(b).concat(a);

第2問:配列の末尾を削除

問題

配列aの最後の要素を削除した新しい配列resultを作ってください。
削除した最後の要素はdeletedItemに入れてください。
ただし、配列aを変更してはいけません。

解答例

const result = [...a];
const deletedItem = result.pop();

解説

1問目とは反対に、追加ではなく削除する問題です。pop()が最適なように思いますが、今回も元の配列を変更してはいけないという条件なので、そのままは使えません。

解答例は、pop()はそのままでは使えないので、先にスプレッド構文を使って配列をコピーします。そして、コピーした配列に対してpop()を行うというコードです。pop()は配列の最後の要素を削除し、その削除した要素を返しますので、resultからも削除されています。

このように元の配列が変更されては困る場合、コピーしてから利用する手法はよく使われます。1問目についてもコピーしてからpush()するというコードでもよいのです。

たとえば配列の順番を逆にするreverse()や並べ替えるsort()などは、事前にコピーしてから操作することも多いのではないでしょうか。

const result = [...a].reverse();

配列のコピーは、スプレッド構文で新しい配列として展開する以外に、次のような方法があります。

// 引数なしのslice()で最初から最後まで取り出した新しい配列を作る
const result = a.slice();
// 引数なしのconcat()でコピーを作る
const result = a.concat();
// Array.from()でコピーを作る
const result = Array.from(a);

その他の解答例として、slice()を使って範囲を指定し、最初から最後の1つ前までの要素と、最後の1つの要素をそれぞれ取得するというコードもありました。

const result = a.slice(0, a.length - 1);
const deletedItem = a.slice(a.length - 1)[0];

最後の1つの取得は次のようにしても良いと思います。

const deletedItem = a[a.length - 1];

なお、最後の要素は新しいブラウザならat()を使って取得することもできます。

const deletedItem = a.at(-1);

Array.prototype.at()

at()は、インデックスを数値で指定するとその要素を返すメソッドです。array[3]のような指定と同じですが、違いとして、負の整数を指定した場合は配列の最後の要素から逆順で指定できます。たとえば、at(-1)は最後の要素を返します。新しいブラウザでも現時点でSafariは対応していないので注意してください。

第3問:配列をつなげる

問題

配列aとbをつなげた新しい配列resultを作ってください。
ただし、配列aもbも変更してはいけません。

const a = [1, 2, 3];
const b = [4, 5, 6];

// result = [1, 2, 3, 4, 5, 6] にする

解答例

const result = [...a, ...b];

解説

1問目、2問目ができるなら、簡単なサービス問題です。解答例では、スプレッド構文を使って2つの配列を展開し、つなげています。1問目、2問目に影響されていたのかもしれませんが、ピクセルグリッドのエンジニアからはスプレッド構文の解答が多かったです。

スプレッド構文はこういった配列を連結する場合にも力を発揮します。

その他の解答では、素直に配列を連携するconcat()を使った例もありました。

const result = a.concat(b);

まとめ

今回は簡単な配列操作の問題と解説でした。スプレッド構文ができてから、配列のメソッドを使わなくてもさまざまな操作ができるようになっています。

スプレッド構文は配列においては主に次の用途で使えます。

  • [...array]: 配列のコピー(クローン)
  • [...array, item]: 要素の追加した新たな配列の作成
  • [...array1, ...array2]: 配列をマージした新たな配列の作成

また、今回は出てきませんでしたが、配列ではないけれど繰り返せるオブジェクト、つまりIterable(イテラブル)である文字列やNodeListといったオブジェクトを配列に変換することもできます。

  • [..."あいうえお"]: 文字列を一文字ずつ展開する
  • [...document.querySelectorAll("div")]: divのNodeListをdivの配列にする

配列を展開して引数として渡すという使い方も一般的です。引数のうち最大の数を返すMath.max()などと組み合わせると便利でしょう。

  • maximum = Math.max(...array);: 数値が入ってる想定のarrayの中で最大の数字を取り出す

コードを見たときにもどういったことが行われているかわかりやすいので、使える場面では積極的に使っていってもよいのではないでしょうか。

次回は残り2問について解説します。

中村 享介
PixelGrid Inc.
Jamstackエンジニア

2009年、JavaScriptの会社として株式会社ピクセルグリッドを設立。 多数のWebリニューアル、新規立ち上げを取り仕切った経験を持ち、情報設計、フロントエンド、クラウド活用、開発フローの効率化を得意とする。 Webをより発展させるため、新しくブラウザに実装された機能の活用事例を数多く生み出しつつ、日々、クラウドサービスを利用した効率のよい制作・開発手法の試行錯誤を続けている。現在の興味はWeb Componentsを使ったマークアップの改善とJamstack。 著書に『WebクリエイティブのためのDOM Scripting』(単著:毎日コミュニケーションズ、2007年)など。ここ数年は書籍の執筆をせず、フロントエンド技術情報メディア「CodeGrid」で精力的に執筆活動を行っている。

この記事についてのご意見・ご感想 この記事をXにポストする

全記事アクセス+月4回配信、月額880円(税込)

CodeGridを購読する

初めてお申し込みの方には、30日間無料でお使いいただけます