オブジェクトでは、キー付きの値のコレクションを保存できます。これは問題ありません。
しかし、多くの場合、1番目、2番目、3番目の要素などを持つ*順序付きコレクション*が必要になります。たとえば、ユーザー、商品、HTML要素などのリストを保存するために必要になります。
ここではオブジェクトを使用するのは不便です。なぜならオブジェクトは要素の順序を管理するためのメソッドを提供しないからです。既存のプロパティの「間」に新しいプロパティを挿入することはできません。オブジェクトはそのような用途向けに設計されていません。
順序付きコレクションを保存するために、Array
という特別なデータ構造が存在します。
宣言
空の配列を作成するための構文は2つあります。
let arr = new Array();
let arr = [];
ほとんどの場合、2番目の構文が使用されます。角括弧の中に初期要素を指定できます。
let fruits = ["Apple", "Orange", "Plum"];
配列要素には、0から始まる番号が付けられます。
要素は角括弧内の番号で取得できます。
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits[0] ); // Apple
alert( fruits[1] ); // Orange
alert( fruits[2] ); // Plum
要素を置き換えることができます。
fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"]
…または、新しい要素を配列に追加します。
fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Pear", "Lemon"]
配列内の要素の合計数はlength
です。
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits.length ); // 3
alert
を使って配列全体を表示することもできます。
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits ); // Apple,Orange,Plum
配列には任意の型の要素を格納できます。
例えば
// mix of values
let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];
// get the object at index 1 and then show its name
alert( arr[1].name ); // John
// get the function at index 3 and run it
arr[3](); // hello
配列はオブジェクトと同様に、カンマで終わることができます。
let fruits = [
"Apple",
"Orange",
"Plum",
];
「末尾のカンマ」スタイルは、すべての行が似たようになるため、項目の挿入/削除が簡単になります。
「at」で最後の要素を取得
配列の最後の要素が必要だとしましょう。
一部のプログラミング言語では、fruits[-1]
のように同じ目的で負のインデックスを使用できます。
ただし、JavaScriptでは機能しません。角括弧のインデックスは文字通りに扱われるため、結果はundefined
になります。
最後の要素のインデックスを明示的に計算してアクセスできます。fruits[fruits.length - 1]
。
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits[fruits.length-1] ); // Plum
少し面倒ではありませんか?変数名を2回記述する必要があります。
幸いなことに、より短い構文があります。fruits.at(-1)
let fruits = ["Apple", "Orange", "Plum"];
// same as fruits[fruits.length-1]
alert( fruits.at(-1) ); // Plum
言い換えれば、arr.at(i)
は
i >= 0
の場合、arr[i]
とまったく同じです。i
の負の値の場合、配列の末尾から後退します。
メソッド pop/push, shift/unshift
キューは配列の最も一般的な用途の1つです。コンピュータサイエンスでは、これは2つの操作をサポートする要素の順序付きコレクションを意味します。
push
は要素を末尾に追加します。shift
は先頭から要素を取得し、キューを前進させ、2番目の要素が1番目になります。
配列は両方の操作をサポートしています。
実際には、これは非常に頻繁に必要になります。たとえば、画面に表示する必要があるメッセージのキューなどです。
配列には別のユースケースがあります。それはスタックという名前のデータ構造です。
それは2つの操作をサポートします。
push
は要素を末尾に追加します。pop
は末尾から要素を取得します。
したがって、新しい要素は常に「末尾」から追加または取得されます。
スタックは通常、トランプのパックとして説明されます。新しいカードは一番上に追加されるか、一番上から取得されます。
スタックの場合、最後にプッシュされたアイテムが最初に受信されます。これはLIFO(Last-In-First-Out)原則とも呼ばれます。キューの場合、FIFO(First-In-First-Out)があります。
JavaScriptの配列は、キューとスタックの両方として機能できます。要素を先頭または末尾のどちらにも追加/削除できます。
コンピュータサイエンスでは、これを許可するデータ構造は両端キューと呼ばれます。
配列の末尾を操作するメソッド
pop
-
配列の最後の要素を抽出し、それを返します。
let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.pop() ); // remove "Pear" and alert it alert( fruits ); // Apple, Orange
fruits.pop()
とfruits.at(-1)
の両方が配列の最後の要素を返しますが、fruits.pop()
はそれを削除することで配列を変更もします。 push
-
配列の末尾に要素を追加します。
let fruits = ["Apple", "Orange"]; fruits.push("Pear"); alert( fruits ); // Apple, Orange, Pear
fruits.push(...)
の呼び出しはfruits[fruits.length] = ...
と同じです。
配列の先頭を操作するメソッド
shift
-
配列の最初の要素を抽出し、それを返します。
let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.shift() ); // remove Apple and alert it alert( fruits ); // Orange, Pear
unshift
-
配列の先頭に要素を追加します。
let fruits = ["Orange", "Pear"]; fruits.unshift('Apple'); alert( fruits ); // Apple, Orange, Pear
メソッド push
と unshift
は一度に複数の要素を追加できます。
let fruits = ["Apple"];
fruits.push("Orange", "Peach");
fruits.unshift("Pineapple", "Lemon");
// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
alert( fruits );
内部
配列は特別な種類のオブジェクトです。プロパティ arr[0]
にアクセスするために使用される角括弧は、実際にはオブジェクトの構文に由来しています。これは基本的に obj[key]
と同じであり、arr
はオブジェクトであり、数値はキーとして使用されます。
それらは、順序付けられたデータコレクションを操作するための特別なメソッドと、length
プロパティを提供することによりオブジェクトを拡張します。しかし、そのコアでは依然としてオブジェクトです。
JavaScriptには8つの基本的なデータ型しかないことを覚えておいてください(詳細についてはデータ型の章を参照してください)。配列はオブジェクトであり、したがってオブジェクトのように動作します。
たとえば、参照でコピーされます。
let fruits = ["Banana"]
let arr = fruits; // copy by reference (two variables reference the same array)
alert( arr === fruits ); // true
arr.push("Pear"); // modify the array by reference
alert( fruits ); // Banana, Pear - 2 items now
…しかし、配列を本当に特別なものにしているのは、その内部表現です。エンジンは、この章の図に描かれているように、要素を連続したメモリ領域に次々と格納しようとします。また、配列を非常に高速に動作させるための他の最適化もあります。
しかし、配列を「順序付きコレクション」として扱うことをやめ、通常のオブジェクトであるかのように扱い始めると、これらはすべて壊れます。
たとえば、技術的にはこれを行うことができます。
let fruits = []; // make an array
fruits[99999] = 5; // assign a property with the index far greater than its length
fruits.age = 25; // create a property with an arbitrary name
これは、配列が基本的にはオブジェクトであるため可能です。任意のプロパティを配列に追加できます。
しかし、エンジンは私たちが配列を通常のオブジェクトとして操作していることを見抜きます。配列固有の最適化はそのようなケースには適していないため、オフになり、そのメリットはなくなります。
配列を誤用する方法
arr.test = 5
のように、数値ではないプロパティを追加します。arr[0]
を追加してからarr[1000]
を追加するなど、穴を作ります(その間に何もありません)。arr[1000]
、arr[999]
のように、配列を逆順に埋めます。
配列は、*順序付けられたデータ*を操作するための特別な構造として考えてください。配列はそれのための特別なメソッドを提供します。配列は、連続した順序付けられたデータを操作するためにJavaScriptエンジン内で慎重に調整されています。このように使用してください。また、任意のキーが必要な場合は、実際には通常のオブジェクト {}
が必要になる可能性が高くなります。
パフォーマンス
メソッド push/pop
は高速に実行されますが、shift/unshift
は低速です。
配列の先頭よりも末尾を操作する方が速いのはなぜですか?実行中に何が起こるかを見てみましょう。
fruits.shift(); // take 1 element from the start
インデックス 0
を持つ要素を取得して削除するだけでは十分ではありません。他の要素も番号を振り直す必要があります。
shift
操作は3つのことを行う必要があります。
- インデックス
0
を持つ要素を削除します。 - すべての要素を左に移動し、インデックス
1
から0
へ、2
から1
へというように番号を振り直します。 length
プロパティを更新します。
配列内の要素が多いほど、移動に時間がかかり、メモリ内の操作が増えます。
同様のことが unshift
でも起こります。配列の先頭に要素を追加するには、最初に既存の要素を右に移動し、インデックスを増やす必要があります。
では、push/pop
はどうでしょうか?それらは何も移動する必要はありません。末尾から要素を抽出するために、pop
メソッドはインデックスをクリアし、length
を短縮します。
pop
操作のアクション
fruits.pop(); // take 1 element from the end
pop
メソッドは他の要素がインデックスを保持するため、何も移動する必要はありません。そのため、非常に高速です。
push
メソッドでも同様です。
ループ
配列項目をループ処理する最も古い方法の1つは、インデックスに対する for
ループです。
let arr = ["Apple", "Orange", "Pear"];
for (let i = 0; i < arr.length; i++) {
alert( arr[i] );
}
しかし、配列には別の形式のループ、for..of
があります。
let fruits = ["Apple", "Orange", "Plum"];
// iterates over array elements
for (let fruit of fruits) {
alert( fruit );
}
for..of
は現在の要素の番号へのアクセスを提供しませんが、ほとんどの場合、それで十分です。そして、より短いです。
技術的には、配列はオブジェクトであるため、for..in
を使用することも可能です。
let arr = ["Apple", "Orange", "Pear"];
for (let key in arr) {
alert( arr[key] ); // Apple, Orange, Pear
}
しかし、それは実際には悪い考えです。それには潜在的な問題があります。
-
for..in
ループは、数値プロパティだけでなく、すべてのプロパティを反復処理します。ブラウザやその他の環境には、いわゆる「配列のような」オブジェクトがあり、配列のように見えます。つまり、
length
とインデックスプロパティを持っていますが、通常は必要としない数値以外のプロパティやメソッドも持つ場合があります。for..in
ループはそれらも列挙してしまいます。したがって、配列のようなオブジェクトを扱う必要がある場合、これらの「余分な」プロパティが問題になる可能性があります。 -
for..in
ループは配列ではなく汎用オブジェクト向けに最適化されているため、10〜100倍遅くなります。もちろん、それでも非常に高速です。高速化が重要になるのはボトルネックの場合だけです。それでも、この違いを認識しておく必要があります。
一般的に、配列には for..in
を使用すべきではありません。
「length」について
length
プロパティは、配列を変更すると自動的に更新されます。正確に言うと、配列内の値の数ではなく、最大の数値インデックスに 1 を加えたものです。
たとえば、大きなインデックスを持つ単一の要素は、大きな長さになります。
let fruits = [];
fruits[123] = "Apple";
alert( fruits.length ); // 124
通常、このような配列は使用しないことに注意してください。
length
プロパティのもう 1 つの興味深い点は、書き込み可能であるということです。
手動で増やすと、特に何も起こりません。しかし、減らすと、配列は切り詰められます。このプロセスは不可逆的です。以下に例を示します。
let arr = [1, 2, 3, 4, 5];
arr.length = 2; // truncate to 2 elements
alert( arr ); // [1, 2]
arr.length = 5; // return length back
alert( arr[3] ); // undefined: the values do not return
したがって、配列をクリアする最も簡単な方法は、arr.length = 0;
です。
new Array()
配列を作成するもう 1 つの構文があります。
let arr = new Array("Apple", "Pear", "etc");
角括弧 []
の方が短いため、めったに使用されません。また、これには注意が必要な機能があります。
new Array
が数値である単一の引数で呼び出されると、項目なしで、指定された長さを持つ配列が作成されます。
どのようにして自滅してしまうかを見てみましょう。
let arr = new Array(2); // will it create an array of [2] ?
alert( arr[0] ); // undefined! no elements.
alert( arr.length ); // length 2
このような驚きを避けるために、本当に何をしているのかを理解している場合を除き、通常は角括弧を使用します。
多次元配列
配列は、それ自体が配列である項目を持つことができます。たとえば、行列を格納するために多次元配列に使用できます。
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
alert( matrix[1][1] ); // 5, the central element
toString
配列には、要素のカンマ区切りのリストを返す独自の toString
メソッドの実装があります。
例えば
let arr = [1, 2, 3];
alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true
また、これを試してみましょう。
alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"
配列には Symbol.toPrimitive
も、有効な valueOf
もありません。toString
変換のみを実装しているため、ここで []
は空文字列になり、[1]
は "1"
に、[1,2]
は "1,2"
になります。
二項プラス "+"
演算子が何らかのものを文字列に追加する場合、それも文字列に変換するため、次のステップは次のようになります。
alert( "" + 1 ); // "1"
alert( "1" + 1 ); // "11"
alert( "1,2" + 1 ); // "1,21"
== で配列を比較しない
JavaScript の配列は、他のいくつかのプログラミング言語とは異なり、演算子 ==
で比較すべきではありません。
この演算子には配列に対する特別な処理はなく、他のオブジェクトと同様に機能します。
ルールを思い出してみましょう。
- 2 つのオブジェクトが
==
で等しいのは、同じオブジェクトへの参照である場合のみです。 ==
の引数の 1 つがオブジェクトで、もう 1 つがプリミティブの場合、オブジェクトは「オブジェクトのプリミティブ変換」の章で説明したように、プリミティブに変換されます。- …互いに
==
で等しく、他には何も等しくないnull
とundefined
を除いて。
厳密な比較 ===
は、型を変換しないため、さらに単純です。
したがって、配列を ==
で比較する場合、まったく同じ配列を参照する 2 つの変数を比較しない限り、それらは決して同じではありません。
例を示します。
alert( [] == [] ); // false
alert( [0] == [0] ); // false
これらの配列は、技術的には異なるオブジェクトです。したがって、それらは等しくありません。==
演算子は、項目ごとの比較を行いません。
プリミティブとの比較も、一見奇妙な結果になる可能性があります。
alert( 0 == [] ); // true
alert('0' == [] ); // false
ここで、両方の場合において、プリミティブを配列オブジェクトと比較します。したがって、配列 []
は比較の目的でプリミティブに変換され、空文字列 ''
になります。
次に、「型変換」の章で説明したように、比較プロセスはプリミティブで続行されます。
// after [] was converted to ''
alert( 0 == '' ); // true, as '' becomes converted to number 0
alert('0' == '' ); // false, no type conversion, different strings
では、どのように配列を比較すればよいのでしょうか?
それは簡単です。==
演算子を使用しないでください。代わりに、ループ内または次の章で説明する反復メソッドを使用して、項目ごとに比較します。
まとめ
配列は、順序付けられたデータ項目を格納および管理するのに適した特殊な種類のオブジェクトです。
宣言
// square brackets (usual)
let arr = [item1, item2...];
// new Array (exceptionally rare)
let arr = new Array(item1, item2...);
new Array(number)
の呼び出しは、指定された長さを持つが、要素のない配列を作成します。
length
プロパティは、配列の長さ、または正確に言うと、最後の数値インデックスに 1 を加えたものです。これは、配列メソッドによって自動調整されます。- 手動で
length
を短縮すると、配列は切り詰められます。
要素の取得
arr[0]
のように、インデックスで要素を取得できます。- また、負のインデックスを許可する
at(i)
メソッドを使用することもできます。i
の値が負の場合、配列の末尾から戻ります。i >= 0
の場合、arr[i]
と同じように動作します。
次の操作で配列をデキューとして使用できます。
push(...items)
は、末尾にitems
を追加します。pop()
は、末尾から要素を削除し、それを返します。shift()
は、先頭から要素を削除し、それを返します。unshift(...items)
は、先頭にitems
を追加します。
配列の要素を反復処理するには
for (let i=0; i<arr.length; i++)
– 最速で動作し、古いブラウザとも互換性があります。for (let item of arr)
– 項目のみを対象とした最新の構文for (let i in arr)
– 絶対に使用しないでください。
配列を比較するには、==
演算子 (および >
, <
など) を使用しないでください。これらは配列に対する特別な処理を行いません。それらは他のオブジェクトとして処理し、それは通常望ましい動作ではありません。
代わりに、for..of
ループを使用して、配列を項目ごとに比較できます。
次の章「配列メソッド」で、配列を操作するためのメソッドについてさらに詳しく学びます。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10 行を超える場合はサンドボックスを使用してください(plnkr、jsbin、codepen…)