GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||
GeezerVison is showing developer.mozilla.org — Array - JavaScript | MDN このページはコミュニティーの尽力で英語から翻訳されました。MDN Web Docs コミュニティーについてもっと知り、仲間になるにはこちらから。
Array
|
| 変更メソッド | 変更しない代替メソッド |
|---|---|
copyWithin() |
代替メソッドなし |
fill() |
代替メソッドなし |
pop() |
slice(0, -1) |
push(v1, v2) |
concat([v1, v2]) |
reverse() |
toReversed() |
shift() |
slice(1) |
sort() |
toSorted() |
splice() |
toSpliced() |
unshift(v1, v2) |
toSpliced(0, 0, v1, v2) |
変更メソッドを変更しない代替メソッドに変更する簡単な方法は、スプレッド構文 や slice() を使って最初にコピーを作成することです。
arr.copyWithin(0, 1, 2); // arr を変更
const arr2 = arr.slice().copyWithin(0, 1, 2); // arr を変更しない
const arr3 = [...arr].copyWithin(0, 1, 2); // arr を変更しない
多くの配列メソッドは、コールバック関数を引数にとります。コールバック関数は配列のそれぞれの要素に対して順次、最大1回呼び出され、コールバック関数の返値はメソッドの返値を決定するために使用されます。これらはすべて同じ形式です。
method(callbackFn, thisArg)
callbackFn は 3 つの引数を取ります。
callbackFn が何を返すかは、呼び出された配列メソッドによって異なります。
引数 thisArg (既定値は undefined)は、callbackFn を呼び出す際に this 値として使用されます。callbackFn から最終的に見える this 値は、通常のルールに従って決定します。もし callbackFn が厳格モードではないのであれば、this の値がプリミティブ型であった場合はオブジェクトにラップされ、undefined/null の場合は globalThis に置き換わります。callbackFn がアロー関数で定義されていた場合は、thisArg 引数は関係ありません。アロー関数には自分自身の this のバインディングがないためです。
callbackFn に渡される引数 array は、反復処理中に別の要素を読み込みたい場合に最も便利です。なぜなら、現在の配列を参照する既存の変数が常に存在するとは限らないからです。通常、反復処理中に配列を変更すべきではありません(反復処理内での初期配列の変更を参照)。しかし、この引数を使用して変更することもできます。配列引数は、map()、filter()、flatMap() などのメソッドの場合、構築中の配列ではありません。構築中の配列にコールバック関数からアクセスする方法はありません。
すべての反復可能オブジェクトはコピー可能で汎用性がありますが、空のスロットでは異なる形で動作します。
反復処理可能なメソッドは、every(), filter(), find(), findIndex(), findLast(), findLastIndex(), flatMap(), forEach(), map(), some() です。
具体的には、every(), find(), findIndex(), findLast(), findLastIndex(), some() は常にすべての要素に対して callbackFn を呼び出すわけではなく、返値が決定するとすぐに反復処理を停止します。
reduce() および reduceRight() メソッドもコールバック関数を取り、配列のそれぞれの要素に対して最大 1 回実行しますが、一般的な反復処理メソッドとは少し異なる形式を持っています(thisArg を受け入れないなど)。
sort() メソッドもコールバック関数を取りますが、反復処理するメソッドではありません。配列の配置を変更し、thisArg を受け入れず、1 つの要素に対して複数回コールバックを呼び出すことがあります。
反復処理メソッドは、以下のように配列を反復処理します(技術的な詳細は省略します)。
function method(callbackFn, thisArg) {
const length = this.length;
for (let i = 0; i < length; i++) {
if (i in this) {
const result = callbackFn.call(thisArg, this[i], i, this);
// 結果に対して何かをする。早く戻ることもある
}
}
}
以下の点に注意してください。
i in this の検査を行うわけではありません。 find、findIndex、findLast、findLastIndex の核メソッドは行いませんが、他のメソッドは行います。length は、ループ開始前に記憶されます。これは、反復処理中の挿入や削除を処理する方法に影響します(反復処理内での初期配列の変更を参照)。for (let i = length - 1; i >= 0; i--))するメソッドは reduceRight()、findLast()、findLastIndex() です。reduce と reduceRight は、シグネチャが若干異なり、常に最初の要素または最後の要素から始めるわけではありません。配列メソッドは常に汎用的であり、配列オブジェクトの内部データにはアクセスしません。配列の要素には length プロパティを通してアクセスし、その位置の要素をアクセスするだけです。つまり、配列風オブジェクトに対しても呼び出すことができます。
const arrayLike = {
0: "a",
1: "b",
length: 2,
};
console.log(Array.prototype.join.call(arrayLike, "+")); // 'a+b'
length プロパティは数値に変換され、0 から 253 - 1 までの範囲に収められます。NaN は 0 となるので、length が存在しないか undefined であっても 0 という値を持つものとして扱われます。
言語は length を安全でない整数に設定することを避けます。もし length が 253 - 1 より大きい数に設定されると、すべての組み込みメソッドは TypeError を発生させます。しかし、配列の length プロパティは、232 - 1 より大きい値に設定されるとエラーが発生するので、メソッドが非配列オブジェクトで呼び出されない限り、安全な整数のしきい値に達することは通常ありません。
Array.prototype.flat.call({}); // []
配列メソッドの中には、配列オブジェクトの length プロパティを設定するものがあります。これらのメソッドは常に正規化後の値を設定するので、length は常に整数値になります。
const a = { length: 0.7 };
Array.prototype.push.call(a);
console.log(a.length); // 0
配列風オブジェクトとは、上記の length の処理で例外が発生しないオブジェクトのことです。具体的に言うと、このようなオブジェクトには実際に length プロパティがあり、0 から length - 1 までの添字つき要素を持つことが期待されます(すべての要素が存在するわけではない場合は、機能的に疎配列と同等となります)。配列メソッドが配列風オブジェクトを処理するとき、 0 より小さいか length - 1 より大きい整数の添字は無視されます。
多くの DOM オブジェクトは、例えば NodeList や HTMLCollection は配列風です。また、arguments オブジェクトも配列風です。自分自身に配列メソッドがなくても、その配列メソッドを呼び出すことができます。
function f() {
console.log(Array.prototype.join.call(arguments, "+"));
}
f("a", "b"); // 'a+b'
Array()新しい Array オブジェクトを生成します。
Array[Symbol.species]Array コンストラクターを返します。
Array.from()新しい Array インスタンスを反復可能 (iterable) または配列風 (array-like) オブジェクトから生成します。
Array.fromAsync()非同期反復可能、反復可能、配列風オブジェクトを元に、新しい Array インスタンスを作成します。
Array.isArray()配列であれば true を、配列でなければ false を返します。
Array.of()可変個の引数から、引数の個数や型に関わらず、新しい Array インスタンスを生成します。
これらのプロパティは Array.prototype で定義されており、すべての Array インスタンスで共有されます。
Array.prototype.constructorこのインスタンスオブジェクトを作成したコンストラクター関数です。 Array インスタンスの場合、初期値は Array コンストラクターです。
Array.prototype[Symbol.unscopables]ES2015 版以前の ECMAScript 標準に含まれておらず、 with による文のバインドの目的には無視されるプロパティ名を含みます。
これらのプロパティはそれぞれの Array インスタンスが自分自身で持つプロパティです。
length配列内の要素数を反映します。
Array.prototype.at()指定された位置にある配列の項目を返します。負の整数も指定可能で、末尾の項目から戻ります。
Array.prototype.concat()この配列に他の配列や値を結合して新しい配列を返します。
Array.prototype.copyWithin()配列内で配列内の連続した要素を複写します。
Array.prototype.entries()新しい配列イテレーターオブジェクトを返します。このオブジェクトは、配列中のそれぞれの位置に対するキー/値の組を保持しています。
Array.prototype.every()配列内で、指定されたテスト関数の条件を満たさない要素を見つけた場合は false を返します。それ以外の場合は true を返します。
Array.prototype.fill()配列内の指定した開始位置から終了位置までの要素を固定値で埋めます。
Array.prototype.filter()指定したフィルタリング関数が true を返す、配列中の要素を格納した新しい配列を生成します。
Array.prototype.find()指定されたたテスト関数を満たす、配列の最初の要素の値を返します。適切な要素が見つからなかった場合は undefined を返します。
Array.prototype.findIndex()指定されたテスト関数を満たす、配列の最初の要素の添字を返します。適切な要素が見つからなかった場合は -1 を返します。
Array.prototype.findLast()指定されたたテスト関数を満たす、配列の最後の要素の値を返します。適切な要素が見つからなかった場合は undefined を返します。
Array.prototype.findLastIndex()指定されたテスト関数を満たす、配列の最後の要素の添字を返します。適切な要素が見つからなかった場合は -1 を返します。
Array.prototype.flat()すべての部分配列の要素を指定された深さまで再帰的に連結した新しい配列を返します。
Array.prototype.flatMap()コールバック関数を呼び出し元の配列の各要素に適用し、その結果を一段階平坦化した新しい配列を返します。
Array.prototype.forEach()配列中のそれぞれの要素について関数を呼び出します。
Array.prototype.includes()この配列が特定の要素を含むかどうか判定し、その結果を true または false で返します。
Array.prototype.indexOf()指定された値と等しい値を持つ最初の(添字の一番小さい)要素の添字を返します。見つからない場合、-1 を返します。
Array.prototype.join()この配列内のすべての要素を、カンマまたは指定された区切り文字列で区切って連結した新しい文字列を返します。
Array.prototype.keys()新しい配列イテレーターを返します。このオブジェクトは配列中の各添字のキーを保持します。
Array.prototype.lastIndexOf()指定された値と等しい値を持つ最後の (添字の一番大きい) 要素の添字を返します。見つからない場合、-1 を返します。
Array.prototype.map()配列内のすべての要素に対して与えられた関数を呼び出し、その結果を格納した新しい配列を生成します。
Array.prototype.pop()配列から最後の要素を取り除き、返値として返します。
Array.prototype.push()配列の最後に 1 個以上の要素を追加し、新しい配列の length を返します。
Array.prototype.reduce()アキュムレーターと配列内のすべての要素に対して (左から右の順で) 関数を適用し、単一の値に還元します。
Array.prototype.reduceRight()アキュムレーターと配列内のすべての要素に対して (右から左の順で) 関数を適用し、単一の値に還元します。
Array.prototype.reverse()配列の要素の順番を逆転させます (最初の要素は最後に、最後の要素は最初になります)。
Array.prototype.shift()配列から最初の要素を取り除き、その要素を返します。
Array.prototype.slice()配列の一部を取り出して新しい配列として返します。
Array.prototype.some()指定されたテスト関数を満たす要素が配列内に見つかった場合、true を返します。そうでなければ、false を返します。
Array.prototype.sort()配列内で要素を整列し、配列を返します。
Array.prototype.splice()配列に対して複数の要素を追加したり取り除いたりします。
Array.prototype.toLocaleString()配列とその要素を表すロケールに従った文字列を返します。Object.prototype.toLocaleString() メソッドを上書きします。
Array.prototype.toReversed()元の配列を変更せずに、要素を逆順に並べた新しい配列を返します。
Array.prototype.toSorted()元の配列を変更せずに、要素を昇順に並べた新しい配列を返します。
Array.prototype.toSpliced()元配列を変更することなく、指定された位置の要素を除去または置き換えた新しい配列を返します。
Array.prototype.toString()配列とその要素を表す文字列を返します。Object.prototype.toString() メソッドを上書きしています。
Array.prototype.unshift()配列の最初に 1 個以上の要素を追加し、配列の変更後の length を返します。
Array.prototype.values()新しい配列イテレーターオブジェクトを返します。このオブジェクトは、配列中の各添字の値を保持します。
Array.prototype.with()指定された位置の要素を指定された値で置き換えた新しい配列を、元の配列に変更を加えることなく返します。
Array.prototype[Symbol.iterator]()既定では values() 関数を返します。
この節では、 JavaScript で一般的な配列操作の例をいくつか紹介します。
メモ: まだ配列の基本に慣れていない場合は、最初に JavaScript の第一歩: 配列 の配列とは何かの説明、およびよくある配列の操作の他の例を読んでみてください。
ここでは、新しい配列を作る 3 通りの方法を示しています。最初のものは配列リテラル記法を使用したもので、次は Array() コンストラクターを使用して、最後は String.prototype.split() を使用して文字列から配列を構築しています。
// 'fruits' 配列が配列リテラル記法で作成されています。
const fruits = ["りんご", "バナナ"];
console.log(fruits.length);
// 2
// 'fruits2' 配列が Array() コンストラクターで作成されています。
const fruits2 = new Array("りんご", "バナナ");
console.log(fruits2.length);
// 2
// 'fruits3' 配列が String.prototype.split() を使用して作成されています。
const fruits3 = "りんご, バナナ".split(", ");
console.log(fruits3.length);
// 2
この例では join() メソッドを使用して、配列 fruits から文字列を生成しています。
const fruits = ["りんご", "バナナ"];
const fruitsString = fruits.join(", ");
console.log(fruitsString);
// "りんご, バナナ"
この例では配列 fruits 内の項目に、配列内の位置を示す添字を指定することでアクセスする方法を示しています。
const fruits = ["りんご", "バナナ"];
// 配列の最初の要素の添字は必ず 0 です。
fruits[0]; // りんご
// 配列の 2 番目の要素の添字は必ず 1 です。
fruits[1]; // バナナ
// 配列の末尾の要素の添字は、常に配列の長さよりも
// 1 だけ小さいものです。
fruits[fruits.length - 1]; // バナナ
// 配列の長さよりも大きな添字を使用すると、
// 'undefined' が返されます。
fruits[99]; // undefined
この例では indexOf() メソッドを使用して、文字列 "バナナ" の位置(添字)を配列 fruits の中から探します。
const fruits = ["りんご", "バナナ"];
console.log(fruits.indexOf("バナナ"));
// 1
この例では、配列 fruits が "バナナ" と "さくらんぼ" を含んでいるかどうかを確認する 2 つの方法を示しています。最初は includes() メソッドで、次に indexOf() メソッドで、添字の値が -1 でないことを確認します。
const fruits = ["りんご", "バナナ"];
fruits.includes("バナナ"); // true
fruits.includes("さくらんぼ"); // false
// indexOf() が -1 を返さない場合、その配列はその項目を持っています。
fruits.indexOf("バナナ") !== -1; // true
fruits.indexOf("さくらんぼ") !== -1; // false
この例では、 push() メソッドを使用して新しい文字列を配列 fruits に追加しています。
const fruits = ["りんご", "バナナ"];
const newLength = fruits.push("みかん");
console.log(fruits);
// ["りんご", "バナナ", "みかん"]
console.log(newLength);
// 3
この例では、 pop() メソッドを使用して配列 fruits から最後の項目を取り除いています。
const fruits = ["りんご", "バナナ", "みかん"];
const removedItem = fruits.pop();
console.log(fruits);
// ["りんご", "バナナ"]
console.log(removedItem);
// みかん
メモ:
pop() は配列の最後の項目を削除するためだけに使用できます。配列の最後にある複数の項目を削除したい場合は、次の例を参照してください。
この例では splice() メソッドを使用して、配列 fruits から最後の 3 つの項目を取り除いています。
const fruits = ["りんご", "バナナ", "いちご", "マンゴー", "さくらんぼ"];
const start = -3;
const removedItems = fruits.splice(start);
console.log(fruits);
// ["りんご", "バナナ"]
console.log(removedItems);
// ["いちご", "マンゴー", "さくらんぼ"]
この例では splice() メソッドを使用して、配列 fruits を最初の 2 項目ちょうどに切り詰めます。
const fruits = ["りんご", "バナナ", "いちご", "マンゴー", "さくらんぼ"];
const start = 2;
const removedItems = fruits.splice(start);
console.log(fruits);
// ["りんご", "バナナ"]
console.log(removedItems);
// ["いちご", "マンゴー", "さくらんぼ"]
この例では shift() メソッドを使用して、最初の項目を配列 fruits から取り除きます。
const fruits = ["りんご", "バナナ"];
const removedItem = fruits.shift();
console.log(fruits);
// ["バナナ"]
console.log(removedItem);
// りんご
メモ:
shift() は最初の項目を配列から取り除く場合にのみ使用できます。複数の項目を配列の先頭から取り除く場合は、次の例を参照してください。
この例では splice() メソッドを使用して、配列 fruits から先頭の 3 項目を取り除きます。
const fruits = ["りんご", "いちご", "さくらんぼ", "バナナ", "マンゴー"];
const start = 0;
const deleteCount = 3;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["バナナ", "マンゴー"]
console.log(removedItems);
// ["りんご", "いちご", "さくらんぼ"]
この例では unshift() メソッドを使用して、配列 fruits の要素 0 に新しい項目を追加します。これを配列の新しい先頭の項目にします。
const fruits = ["バナナ", "マンゴー"];
const newLength = fruits.unshift("いちご");
console.log(fruits);
// ["いちご", "バナナ", "マンゴー"]
console.log(newLength);
// 3
この例では splice() メソッドを使用して、文字列 "バナナ" を配列 fruits から取り除きます。 "バナナ" の位置の添字を指定します。
const fruits = ["いちご", "バナナ", "マンゴー"];
const start = fruits.indexOf("バナナ");
const deleteCount = 1;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["いちご", "マンゴー"]
console.log(removedItems);
// ["バナナ"]
この例では splice() メソッドを使用して、文字列 "バナナ" および "いちご" を配列 fruits から取り除きます。 "バナナ" の位置の添字と共に、取り除く項目の総数を指定します。
const fruits = ["りんご", "バナナ", "いちご", "マンゴー"];
const start = 1;
const deleteCount = 2;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["りんご", "マンゴー"]
console.log(removedItems);
// ["バナナ", "いちご"]
この例では splice() メソッドを使用して、配列 fruits 内の 2 つの項目を置き換えます。
const fruits = ["りんご", "バナナ", "いちご"];
const start = -2;
const deleteCount = 2;
const removedItems = fruits.splice(
start,
deleteCount,
"マンゴー",
"さくらんぼ",
);
console.log(fruits);
// ["りんご", "マンゴー", "さくらんぼ"]
console.log(removedItems);
// ["バナナ", "いちご"]
この例では for...of ループを使用して配列 fruits を走査し、それぞれの項目をコンソールへログ出力します。
const fruits = ["りんご", "マンゴー", "さくらんぼ"];
for (const fruit of fruits) {
console.log(fruit);
}
// りんご
// マンゴー
// さくらんぼ
ただし for...of は配列を走査する数々の方法の一つにすぎません。他の方法はループと反復処理を参照したり、 every(), filter(), flatMap(), map(), reduce(), reduceRight() の各メソッドのドキュメントを参照したり、また forEach() メソッドを使用している次の例を参照したりしてください。
この例では forEach() メソッドを使用して、配列 fruits のそれぞれの要素に対して関数を呼び出しています。この関数ではそれぞれの項目を、その項目の添字の番号と共にコンソールへログ出力します。
const fruits = ["りんご", "マンゴー", "さくらんぼ"];
fruits.forEach((item, index, array) => {
console.log(item, index);
});
// りんご 0
// マンゴー 1
// さくらんぼ 2
この例では concat() メソッドを使用して、配列 fruits を配列 moreFruits と結合子、新しい配列 combinedFruits を生成します。 fruits と moreFruits は変更されないことに注意してください。
const fruits = ["りんご", "バナナ", "いちご"];
const moreFruits = ["マンゴー", "さくらんぼ"];
const combinedFruits = fruits.concat(moreFruits);
console.log(combinedFruits);
// ["りんご", "バナナ", "いちご", "マンゴー", "さくらんぼ"]
// 配列 'fruits' は変更されないままです。
console.log(fruits);
// ["りんご", "バナナ", "いちご"]
// 配列 'moreFruits' も変更されないままです。
console.log(moreFruits);
// ["マンゴー", "さくらんぼ"]
この例では、既存の配列 fruits から新しい配列を生成する方法を 3 通り示します。最初のものはスプレッド構文を使用するもので、次は from() メソッドを使用するもの、その次は slice() メソッドを使用するものです。
const fruits = ["いちご", "マンゴー"];
// スプレッド構文でコピーを作成します。
const fruitsCopy = [...fruits];
// ["いちご", "マンゴー"]
// from() メソッドでコピーを作成します。
const fruitsCopy2 = Array.from(fruits);
// ["いちご", "マンゴー"]
// slice() メソッドでコピーを作成します。
const fruitsCopy3 = fruits.slice();
// ["いちご", "マンゴー"]
組み込みの配列コピー操作(スプレッド構文, Array.from(), Array.prototype.slice(), Array.prototype.concat())はすべて、シャローコピーを生成します。配列のディープコピーを得る場合は、 JSON.stringify() で配列を JSON 文字列に変換し、 JSON.parse() でその文字列を新しい配列に戻せば、元の配列から完全に独立した新しい配列ができます。
const fruitsDeepCopy = JSON.parse(JSON.stringify(fruits));
また、 structuredClone() メソッドを使用してディープコピーを作成することもできます。これは、ソース内の移譲可能オブジェクトを、コピーするのではなく、新しいコピーに移譲できる利点があります。
最後に、既存の配列を新しい変数に代入しても、配列やその要素のコピーは作成されないことを理解することが重要です。つまり、元の配列の名前と新しい変数の名前は、まったく同じオブジェクトの名前に過ぎません(したがって、常に厳密等価であると評価されます)。したがって、元の配列の値や新しい変数の値に何らかの変更を加えると、もう片方も変更されます。
const fruits = ["いちご", "マンゴー"];
const fruitsAlias = fruits;
// 'fruits' と 'fruitsAlias' は同じオブジェクトとなり、厳密等価になります。
fruits === fruitsAlias; // true
// 配列 'fruits' を変更すると、 'fruitsAlias' も変化します。
fruits.unshift("りんご", "バナナ");
console.log(fruits);
// ['りんご', 'バナナ', 'いちご', 'マンゴー']
console.log(fruitsAlias);
// ['りんご', 'バナナ', 'いちご', 'マンゴー']
以下では、文字列の二次元配列としてチェス盤を生成しています。最初の動きは 'p' を board[6][4] から board[4][4] にコピーすることで行います。古い位置 [6][4] は空白にします。
const board = [
["R", "N", "B", "Q", "K", "B", "N", "R"],
["P", "P", "P", "P", "P", "P", "P", "P"],
[" ", " ", " ", " ", " ", " ", " ", " "],
[" ", " ", " ", " ", " ", " ", " ", " "],
[" ", " ", " ", " ", " ", " ", " ", " "],
[" ", " ", " ", " ", " ", " ", " ", " "],
["p", "p", "p", "p", "p", "p", "p", "p"],
["r", "n", "b", "q", "k", "b", "n", "r"],
];
console.log(`${board.join("\n")}\n\n`);
// キングの前のポーンを 2 つ前へ移動
board[4][4] = board[6][4];
board[6][4] = " ";
console.log(board.join("\n"));
出力を示します。
R,N,B,Q,K,B,N,R P,P,P,P,P,P,P,P , , , , , , , , , , , , , , , , , , , , , , , , , , , , p,p,p,p,p,p,p,p r,n,b,q,k,b,n,r R,N,B,Q,K,B,N,R P,P,P,P,P,P,P,P , , , , , , , , , , , , , , , , , ,p, , , , , , , , , , p,p,p,p, ,p,p,p r,n,b,q,k,b,n,r
const values = [];
for (let x = 0; x < 10; x++) {
values.push([2 ** x, 2 * x ** 2]);
}
console.table(values);
結果は次のようになります。
// 最初の列は添字です 0 1 0 1 2 2 2 4 8 3 8 18 4 16 32 5 32 50 6 64 72 7 128 98 8 256 128 9 512 162
正規表現 (RegExp) と文字列の照合結果から配列を生成することができます。この配列には、プロパティと、一致したものに関する情報を提供する要素を持ちます。このような配列は RegExp.prototype.exec() や String.prototype.match() から返されます。
例えば、次のようになります。
// 1 文字の d と、続く 1 文字以上の b と、続く 1 文字の d に一致します
// 一致した b およびそれに続く d を記憶します
// 大文字小文字は区別しません
const myRe = /d(b+)(d)/i;
const execResult = myRe.exec("cdbBdbsbz");
console.log(execResult.input); // 'cdbBdbsbz'
console.log(execResult.index); // 1
console.log(execResult); // [ "dbBd", "bB", "d" ]
照合結果についての詳しい情報は、 RegExp.prototype.exec() および String.prototype.match() のページを参照してください。
反復処理メソッドは、呼び出された配列を変更しませんが、callbackFn として提供される関数は変更することができます。覚えておくべき重要な原則は、0 から arrayLength - 1 までの要素のみが参照されるということです。ここで、arrayLength は配列メソッドが最初に呼び出された時点での配列の長さですが、コールバックに渡される要素は、その要素が参照された時点での値です。したがって、
callbackFn は、反復処理メソッドの呼び出しが開始された際に、配列の初期長を超えて追加された要素を処理しません。callbackFn が再度呼び出されることはありません。callbackFn によって変更された場合、callbackFn に渡される値は、その要素が処理された時点での値となります。 除去された要素は処理されません。警告: 上記のような同時進行の変更は、理解しにくいコードにつながる可能性が高く、一般的に避けるべきです(特別な場合を除く)。
次の例では、forEach メソッドを例として使用していますが、他にも昇順で要素を巡回するメソッドであれば、同じように動作します。 最初のヘルパー関数を定義します。
function testSideEffect(effect) {
const arr = ["e1", "e2", "e3", "e4"];
arr.forEach((elem, index, arr) => {
console.log(`配列: [${arr.join(", ")}], 添字: ${index}, 要素: ${elem}`);
effect(arr, index);
});
console.log(`最終的な配列: [${arr.join(", ")}]`);
}
まだ処理されていない要素を変更した場合、要素に到達したときに見えます。
testSideEffect((arr, index) => {
if (index + 1 < arr.length) arr[index + 1] += "*";
});
// 配列: [e1, e2, e3, e4], 添字: 0, 要素: e1
// 配列: [e1, e2*, e3, e4], 添字: 1, 要素: e2*
// 配列: [e1, e2*, e3*, e4], 添字: 2, 要素: e3*
// 配列: [e1, e2*, e3*, e4*], 添字: 3, 要素: e4*
// 最終的な配列: [e1, e2*, e3*, e4*]
既に処理した位置の要素を変更しても、反復処理する挙動は変わりませんが、配列はその後で異なったものになります。
testSideEffect((arr, index) => {
if (index > 0) arr[index - 1] += "*";
});
// 配列: [e1, e2, e3, e4], 添字: 0, 要素: e1
// 配列: [e1, e2, e3, e4], 添字: 1, 要素: e2
// 配列: [e1*, e2, e3, e4], 添字: 2, 要素: e3
// 配列: [e1*, e2*, e3, e4], 添字: 3, 要素: e4
// 最終的な配列: [e1*, e2*, e3*, e4]
未処理の要素に、初期配列の長さよりも小さい要素を n 個挿入すると、それらの要素が処理済みになります。これで初期配列の長さを超える添字を持つようになった元の配列の最後の n 個の要素は、処理されません。
testSideEffect((arr, index) => {
if (index === 1) arr.splice(2, 0, "new");
});
// 配列: [e1, e2, e3, e4], 添字: 0, 要素: e1
// 配列: [e1, e2, e3, e4], 添字: 1, 要素: e2
// 配列: [e1, e2, new, e3, e4], 添字: 2, 要素: new
// 配列: [e1, e2, new, e3, e4], 添字: 3, 要素: e3
// 最終的な配列: [e1, e2, new, e3, e4]
// e4 is not visited because it now has index 4
配列の最初の要素数よりも大きな添字を持つ要素を n 個挿入しても、それらの要素が処理されることはありません。
testSideEffect((arr) => arr.push("new"));
// 配列: [e1, e2, e3, e4], 添字: 0, 要素: e1
// 配列: [e1, e2, e3, e4, new], 添字: 1, 要素: e2
// 配列: [e1, e2, e3, e4, new, new], 添字: 2, 要素: e3
// 配列: [e1, e2, e3, e4, new, new, new], 添字: 3, 要素: e4
// 最終的な配列: [e1, e2, e3, e4, new, new, new, new]
既に処理した要素に n 個の要素を挿入しても、それらが処理されることはありませんが、残りの要素が n 個分後ろにずれるため、現在の要素と、その前の n - 1 個の要素が再び処理されることになります。
testSideEffect((arr, index) => arr.splice(index, 0, "new"));
// 配列: [e1, e2, e3, e4], 添字: 0, 要素: e1
// 配列: [new, e1, e2, e3, e4], 添字: 1, 要素: e1
// 配列: [new, new, e1, e2, e3, e4], 添字: 2, 要素: e1
// 配列: [new, new, new, e1, e2, e3, e4], 添字: 3, 要素: e1
// 最終的な配列: [new, new, new, new, e1, e2, e3, e4]
// e1は、常にシフトバックされるため、処理され続けます。
未処理の要素から n 個の要素を削除すると、それらはもはや処理されなくなります。配列が縮小したため、最後の n 回の反復処理では、範囲外の添字が処理されます。メソッドが存在しない要素を無視する場合(配列メソッドと空のスロットを参照)、最後の n 回の反復処理はスキップされます。そうでなければ、それらは undefined となります。
testSideEffect((arr, index) => {
if (index === 1) arr.splice(2, 1);
});
// 配列: [e1, e2, e3, e4], 添字: 0, 要素: e1
// 配列: [e1, e2, e3, e4], 添字: 1, 要素: e2
// 配列: [e1, e2, e4], 添字: 2, 要素: e4
// 最終的な配列: [e1, e2, e4]
// 範囲外なので要素 3 は処理しません。
// Compare this with find(), which treats nonexistent indexes as undefined:
const arr2 = ["e1", "e2", "e3", "e4"];
arr2.find((elem, index, arr) => {
console.log(`配列: [${arr.join(", ")}], 添字: ${index}, 要素: ${elem}`);
if (index === 1) arr.splice(2, 1);
return false;
});
// 配列: [e1, e2, e3, e4], 添字: 0, 要素: e1
// 配列: [e1, e2, e3, e4], 添字: 1, 要素: e2
// 配列: [e1, e2, e4], 添字: 2, 要素: e4
// 配列: [e1, e2, e4], 添字: 3, 要素: undefined
既に処理した要素から n 個の要素を削除しても、それらが削除される前に処理されていたという事実は変わりません。配列が縮小されたため、現在の要素の次の n 個の要素はスキップされます。このメソッドが存在しない添字を無視する場合は、最後の n 回の反復処理がスキップされます。そうでない場合は、undefined が返されます。
testSideEffect((arr, index) => arr.splice(index, 1));
// 配列: [e1, e2, e3, e4], 添字: 0, 要素: e1
// e2 は処理されない。e2 の要素は 0 となり、既に処理されているため
// 配列: [e2, e3, e4], 添字: 1, 要素: e3
// e4 は処理されない。e4 の要素は 1 となり、既に処理されているため
// 最終的な配列: [e2, e4]
// 添字 2 は範囲外なので、アクセスされない
// find() は、存在しない要素を undefined として扱います。これと比較してください。
const arr2 = ["e1", "e2", "e3", "e4"];
arr2.find((elem, index, arr) => {
console.log(`配列: [${arr.join(", ")}], 添字: ${index}, 要素: ${elem}`);
arr.splice(index, 1);
return false;
});
// 配列: [e1, e2, e3, e4], 添字: 0, 要素: e1
// 配列: [e2, e3, e4], 添字: 1, 要素: e3
// 配列: [e2, e4], 添字: 2, 要素: undefined
// 配列: [e2, e4], 添字: 3, 要素: undefined
添字の降順で反復処理するメソッドでは、挿入により要素がスキップされ、削除により要素が複数回アクセスされます。 上記のコードを自分で調整して、効果を確認してください。
| 仕様書 |
|---|
| ECMAScript® 2027 Language Specification # sec-array-objects |