配列の拡張

スプレッド演算子

意味

スプレッド演算子 (spread) は 3 つのドット (...) です。これは、配列をカンマ区切りのパラメーター シーケンスに変換する、残りのパラメーターの逆演算に似ています。

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

この演算子は主に関数呼び出しに使用されます。

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

const numbers = [4, 38];
add(...numbers) // 42

上記のコードでは、行 array.push(...items)add(...numbers) は両方とも関数呼び出しであり、どちらもスプレッド演算子を使用しています。この演算子は、配列をパラメータのシーケンスに変換します。

スプレッド演算子は通常の関数パラメータと組み合わせて使用​​できるため、非常に柔軟です。

function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);

式はスプレッド演算子の後に配置することもできます。

const arr = [
  ...(x > 0 ? ['a'] : []),
  'b',
];

スプレッド演算子の後に空の配列が続いた場合、効果はありません。

[...[], 1]
// [1]

スプレッド演算子は、関数が呼び出される場合にのみ括弧内に置くことができ、それ以外の場合はエラーが報告されることに注意してください。

(...[1, 2])
// Uncaught SyntaxError: Unexpected number

console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number

console.log(...[1, 2])
// 1 2

上記 3 つのケースでは、展開演算子が括弧内に配置されていますが、最初の 2 つのケースでは、展開演算子が配置されている括弧が関数呼び出しではないため、エラーが報告されます。

置換関数のApply()メソッド

スプレッド演算子は配列を展開できるため、配列を関数パラメータに変換するために apply() メソッドが必要なくなりました。

// ES5の書き方
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6の書き方
function f(x, y, z) {
  // ...
}
let args = [0, 1, 2];
f(...args);

以下は、apply() メソッドを置き換えるスプレッド演算子の実際の例です。Math.max() メソッドは、配列の最大要素を見つけるための記述方法を簡素化するために使用されます。

// ES5の書き方
Math.max.apply(null, [14, 3, 77])

// ES6の書き方
Math.max(...[14, 3, 77])

// と同等
Math.max(14, 3, 77);

上記のコードでは、javascript には配列の最大要素を見つける関数が提供されていないため、「Math.max()」関数を使用して配列をパラメータ シーケンスに変換し、最大値を見つけることしかできません。スプレッド演算子を使用すると、Math.max() を直接使用できます。

別の例は、push() 関数を使用して、ある配列を別の配列の末尾に追加することです。

// ES5の書き方
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6の書き方
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);

上記のコードを記述する ES5 の方法では、push() メソッドのパラメータを配列にすることはできないため、apply() メソッドを通じて push() メソッドを使用する必要があります。スプレッド演算子を使用すると、配列を push() メソッドに直接渡すことができます。

別の例を示します。

// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))

// ES6
new Date(...[2015, 1, 1]);

スプレッド演算子の適用

(1)配列のコピー

配列は複合データ型です。直接コピーする場合は、新しい配列を複製するのではなく、基になるデータ構造へのポインターをコピーするだけです。

const a1 = [1, 2];
const a2 = a1;

a2[0] = 2;
a1 // [2, 2]

上記のコードでは、「a2」は「a1」のクローンではなく、同じデータを指す別のポインタです。 「a2」を変更すると、「a1」の変更が直接反映されます。

ES5 では、配列をコピーする場合にのみ回避策を使用できます。

const a1 = [1, 2];
const a2 = a1.concat();

a2[0] = 2;
a1 // [1, 2]

上記のコードでは、「a1」は元の配列のクローンを返し、「a2」を変更しても「a1」には影響しません。

スプレッド演算子は、配列をコピーする便利な方法を提供します。

const a1 = [1, 2];
//書き方1
const a2 = [...a1];
//書き方2
const [...a2] = a1;

上記 2 つの書き方では、「a2」は「a1」のクローンです。

(2) 配列を結合する

スプレッド演算子は、配列の組み合わせを記述する新しい方法を提供します。

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 マージされた配列
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 マージされた配列
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

ただし、これら 2 つの方法は浅いコピーであるため、使用する場合は注意が必要です。

const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];

const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];

a3[0] === a1[0] // true
a4[0] === a1[0] // true

上記のコードでは、「a3」と「a4」は 2 つの異なるメソッドを使用してマージされた新しい配列ですが、そのメンバーはすべて元の配列メンバーへの参照であり、浅いコピーです。参照が指す値が変更されると、新しい配列に同期的に反映されます。

(3) 構造化代入との組み合わせ

スプレッド演算子を構造化代入と組み合わせて配列を生成できます。

// ES5
a = list[0], rest = list.slice(1)

// ES6
[a, ...rest] = list

さらにいくつかの例を示します。

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest  // []

const [first, ...rest] = ["foo"];
first  // "foo"
rest   // []

配列の代入にスプレッド演算子を使用する場合、パラメータの最後の位置にのみ配置できます。配置しない場合はエラーが報告されます。

const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错

const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 报错

(4)文字列

スプレッド演算子は、文字列を実数の配列に変換することもできます。

[...'hello']
// [ "h", "e", "l", "l", "o" ]

上記の記述方法には、4 バイトの Unicode 文字を正しく識別できるという重要な利点があります。

'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3

上記のコードを記述する最初の方法では、javascript は 4 バイトの Unicode 文字を 2 文字として認識します。スプレッド演算子を使用すると、この問題は発生しません。したがって、文字列の長さを正しく返す関数は次のように記述できます。

function length(str) {
  return [...str].length;
}

length('x\uD83D\uDE80y') // 3

4 バイトの Unicode 文字の操作を伴う関数にはすべて、この問題があります。したがって、代わりにスプレッド演算子を使用することが最善です。

let str = 'x\uD83D\uDE80y';

str.split('').reverse().join('')
// 'y\uDE80\uD83Dx'

[...str].reverse().join('')
// 'y\uD83D\uDE80x'

上記のコードで、拡張演算子が使用されていない場合、文字列の reverse() 操作は正しくありません。

(5) Iterator インターフェイスを実装するオブジェクト

Iterator インターフェイス (「Iterator」の章を参照) を定義するオブジェクトは、スプレッド演算子を使用して実数配列に変換できます。

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

上記のコードでは、querySelectorAll() メソッドは NodeList オブジェクトを返します。これは配列ではなく、配列のようなオブジェクトです。このとき、NodeList オブジェクトは Iterator を実装しているため、spread 演算子はそれを実数の配列に変換できます。

Number.prototype[Symbol.iterator] = function*() {
  let i = 0;
  let num = this.valueOf();
  while (i < num) {
    yield i++;
  }
}

console.log([...5]) // [0, 1, 2, 3, 4]

上記のコードでは、Number オブジェクトのトラバーサー インターフェイスが最初に定義され、拡張演算子が 5Number インスタンスに自動的に変換した後、このインターフェイスが呼び出され、カスタマイズされた結果が返されます。

Iterator インターフェイスを実装していない配列のようなオブジェクトの場合、スプレッド演算子はそれらを真の配列に変換できません。

let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

// TypeError: 反復不可能なオブジェクトを拡散できません。
let arr = [...arrayLike];

上記のコードでは、arrayLike は配列のようなオブジェクトですが、Iterator インターフェイスがデプロイされていないため、展開演算子はエラーを報告します。このとき、Array.fromメソッドを使用して、arrayLikeを実際の配列に変換できます。

(6)マップとセット構造、ジェネレーター機能

スプレッド演算子は内部でデータ構造の Iterator インターフェイスを呼び出すため、オブジェクトが Iterator インターフェイスを持つ限り、Map 構造体などのスプレッド演算子を使用できます。

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()] // [1, 2, 3];

Generator 関数は実行後、トラバーサー オブジェクトを返すため、スプレッド演算子も使用できます。

const go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3]

上記のコードでは、変数 go はジェネレーター関数であり、実行後、このトラバーサー オブジェクトに対してスプレッド演算子を実行すると、内部トラバーサルによって取得された値が配列に変換されます。

Iterator インターフェイスのないオブジェクトでスプレッド演算子を使用すると、エラーが報告されます。

const obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object

Array.from()

Array.from() メソッドは、配列のようなオブジェクトと反復可能なオブジェクト (ES6 の新しいデータ構造 Set および Map を含む) の 2 種類のオブジェクトを実際の配列に変換するために使用されます。

以下は配列のようなオブジェクトで、Array.from() はそれを実際の配列に変換します。

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5の書き方
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6の書き方
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

実際のアプリケーションでは、一般的な配列のようなオブジェクトは、DOM 操作によって返される NodeList コレクションと、関数内の arguments オブジェクトです。 Array.from() はそれらを実際の配列に変換できます。

//NodeList オブジェクト
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
  return p.textContent.length > 100;
});

// arguments 对象
function foo() {
  var args = Array.from(arguments);
  // ...
}

上記のコードでは、querySelectorAll() メソッドは配列のようなオブジェクトを返します。このオブジェクトを実際の配列に変換してから、filter() メソッドを使用できます。

Iterator インターフェースのデータ構造がデプロイされていれば、Array.from() で配列に変換できます。

Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

上記のコードでは、string 構造体と Set 構造体の両方に Iterator インターフェイスがあるため、「Array.from()」によって実数の配列に変換できます。

引数が実数配列の場合、「Array.from()」は同一の新しい配列を返します。

Array.from([1, 2, 3])
// [1, 2, 3]

スプレッド演算子 (...) は一部のデータ構造を配列に変換することもできることを思い出してください。

//引数オブジェクト
function foo() {
  const args = [...arguments];
}

//NodeList オブジェクト
[...document.querySelectorAll('div')]

スプレッド演算子の背後で呼び出されるのは反復子インターフェイス (Symbol.iterator) です。オブジェクトがこのインターフェイスを展開しない場合、変換できません。 Array.from() メソッドは配列のようなオブジェクトもサポートします。いわゆる配列のようなオブジェクトには、重要な特性が 1 つだけあります。それは、「length」属性を持たなければならないということです。したがって、length 属性を持つオブジェクトはすべて、Array.from() メソッドを通じて配列に変換できますが、スプレッド演算子は現時点では変換できません。

Array.from({ length: 3 });
// [ undefined, undefined, undefined ]

上記のコードでは、Array.from() は 3 つのメンバーを含む配列を返し、各位置の値は 未定義 です。スプレッド演算子はこのオブジェクトを変換できません。

このメソッドをデプロイしていないブラウザの場合は、代わりに Array.prototype.slice() メソッドを使用できます。

const toArray = (() =>
  Array.from ? Array.from : obj => [].slice.call(obj)
)();

Array.from() は 2 番目のパラメータとして関数を受け入れることもできます。これは、配列の map() メソッドと同様に、各要素を処理し、処理された値を返された配列に入れるために使用されます。

Array.from(arrayLike, x => x * x);
// と同等
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

次の例では、DOM ノードのセットのテキスト コンテンツを抽出します。

let spans = document.querySelectorAll('span.name');

// map()
let names1 = Array.prototype.map.call(spans, s => s.textContent);

// Array.from()
let names2 = Array.from(spans, s => s.textContent)

次の例では、ブール値 false を持つ配列のメンバーを 0 に変換します。

Array.from([1, , 2, , 3], (n) => n || 0)
// [1, 0, 2, 0, 3]

別の例は、さまざまなデータ型を返すことです。

function typesOf () {
  return Array.from(arguments, value => typeof value)
}
typesOf(null, [], NaN)
// ['object', 'object', 'number']

this キーワードが map() 関数で使用されている場合、Array.from() の 3 番目のパラメータを渡して this をバインドすることもできます。

Array.from() はさまざまな値を実際の配列に変換でき、map 関数も提供します。これが実際に意味するのは、プリミティブ データ構造がある限り、最初にその値を処理してから正規の配列構造に変換し、その後、多数の配列メソッドを使用できるということです。

Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']

上記のコードでは、「Array.from()」の最初のパラメータは、2 番目のパラメータが実行される回数を指定します。この機能により、このメソッドの使用法が非常に柔軟になります。

Array.from() の別の用途は、文字列を配列に変換し、文字列の長さを返すことです。さまざまな Unicode 文字を正しく処理できるため、javascript が「\uFFFF」より大きい Unicode 文字を 2 文字としてカウントするというバグを回避できます。

function countSymbols(string) {
  return Array.from(string).length;
}

Array.of()

Array.of() メソッドは、一連の値を配列に変換するために使用されます。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

このメソッドの主な目的は、配列コンストラクター Array() の欠点を補うことです。パラメータの数が異なるため、Array() の動作も異なります。

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

上記のコードでは、「Array()」メソッドはパラメータがない場合、1 つのパラメータ、または 3 つのパラメータがある場合に異なる結果を返します。 Array() は、パラメータの数が 2 以上の場合にのみ、パラメータで構成される新しい配列を返します。パラメーターに正の整数が 1 つだけある場合、実際には配列の長さを指定します。

Array.of() は基本的に Array() または new Array() を置き換えるために使用でき、異なるパラメーターによるオーバーロードはありません。その動作は非常に均一です。

Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]

Array.of() は常に引数値の配列を返します。パラメータがない場合は、空の配列が返されます。

Array.of() メソッドは、次のコードでシミュレートできます。

function ArrayOf(){
  return [].slice.call(arguments);
}

インスタンスメソッド: copyWithin()

配列インスタンスの copyWithin() メソッドは、指定された位置にあるメンバーを現在の配列内の他の位置にコピーし (元のメンバーは上書きされます)、現在の配列を返します。つまり、このメソッドを使用すると、現在の配列が変更されます。

Array.prototype.copyWithin(target, start = 0, end = this.length)

3 つのパラメータを受け入れます。

  • target (必須): データの置換を開始する位置。負の値の場合は逆数を表します。
  • start (オプション): この位置からデータの読み取りを開始します。デフォルトは 0 です。負の値の場合は最後から数えることを意味します。
  • end (オプション): この位置に到達する前にデータの読み取りを停止します。デフォルトは配列の長さと同じです。負の値の場合は最後から数えることを意味します。

これら 3 つのパラメータはすべて数値である必要があります。そうでない場合は、自動的に数値に変換されます。

[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]

上記のコードは、配列の位置 3 から配列の末尾までのメンバー (4 と 5) が位置 0 から始まる位置にコピーされ、その結果、元の 1 と 2 が上書きされることを意味します。

さらにいくつかの例を示します。

//位置 3 を位置 0 にコピー
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

// -2 は位置 3 に相当し、-1 は位置 4 に相当します。
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

//位置 3 を位置 0 にコピー
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}

// 位置 2 を配列の末尾に移動し、位置 0 にコピーします
let i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]

// TypedArray の copyWithin メソッドをデプロイしないプラットフォームの場合
// 以下の記述方法を使用する必要があります
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]

インスタンス メソッド: find()、findIndex()、findLast()、findLastIndex()

配列インスタンスの find() メソッドは、条件を満たす最初の配列メンバーを見つけるために使用されます。そのパラメータはコールバック関数であり、戻り値が「true」である最初のメンバーが見つかるまで、すべての配列メンバーがコールバック関数を順番に実行し、その後そのメンバーを返します。一致するメンバーが存在しない場合は、「未定義」が返されます。

[1, 4, -5, 10].find((n) => n < 0)
// -5

上記のコードは、配列内の 0 未満の最初のメンバーを検索します。

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

上記のコードでは、find() メソッドのコールバック関数は、現在値、現在位置、元の配列の 3 つのパラメーターを受け入れることができます。

配列インスタンスの findIndex() メソッドの使用法は、find() メソッドと非常によく似ており、すべてのメンバーが基準を満たさない場合は、条件を満たす最初の配列メンバーの位置を返します。 「-1」。

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

どちらのメソッドも 2 番目のパラメータ、つまりコールバック関数をバインドするために使用される「this」オブジェクトを受け入れることができます。

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26

上記のコードでは、find() 関数は 2 番目のパラメーターである person オブジェクトを受け取り、コールバック関数の this オブジェクトは person オブジェクトを指します。

さらに、どちらのメソッドも NaN を見つけることができ、配列の indexOf() メソッドの欠点を補います。

[NaN].indexOf(NaN)
// -1

[NaN].findIndex(y => Object.is(NaN, y))
// 0

上記のコードでは、indexOf() メソッドは配列の NaN メンバーを識別できませんが、findIndex() メソッドは Object.is() メソッドの助けを借りて識別できます。

find()findIndex() はどちらも配列の位置 0 から開始して逆方向にチェックします。 ES2022 配列の最後のメンバーから開始する 2 つの新しいメソッド findLast()findLastIndex() を追加しました。他のすべてを同じにして、順番に前進します。

const array = [
  { value: 1 },
  { value: 2 },
  { value: 3 },
  { value: 4 }
];

array.findLast(n => n.value % 2 === 1); // { value: 3 }
array.findLastIndex(n => n.value % 2 === 1);

上の例では、findLast()findLastIndex() は配列の末尾から開始して、value 属性が奇数である最初のメンバーを探します。結果として、メンバーは { value: 3 } となり、位置は位置 2 になります。

インスタンスメソッド: fill()

fill メソッドは、指定された値で配列を埋めます。

['a', 'b', 'c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]

上記のコードは、fill メソッドが空の配列を初期化するのに非常に便利であることを示しています。配列内の既存の要素はすべて消去されます。

fill メソッドは、塗りつぶしの開始位置と終了位置を指定するために使用される 2 番目と 3 番目のパラメーターも受け入れることができます。

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

上記のコードは、「fill」メソッドが位置 1 から開始し、元の配列に 7 を埋め、位置 2 の前で終了することを示しています。

塗りつぶされた型がオブジェクトの場合、割り当てられるオブジェクトはディープ コピー オブジェクトではなく、同じメモリ アドレスであることに注意してください。

let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5], [5], [5]]

インスタンスメソッド:entrys()、keys()、values()

ES6 は、配列を反復処理するための 3 つの新しいメソッド entries()keys()、および values() を提供します。どちらもイテレータ オブジェクトを返します (詳細については「イテレータ」の章を参照)。これは for...of ループを使用して走査できます。唯一の違いは、keys() がキー名と の走査であることです。 value() はキーと値のペアの走査であり、 entries() はキーと値のペアの走査です。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

「for...of」ループを使用しない場合は、トラバースするトラバーサー オブジェクトの「next」メソッドを手動で呼び出します。

let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

インスタンスメソッド:includes()

Array.prototype.includes メソッドは、文字列の includes メソッドと同様に、配列に指定された値が含まれているかどうかを示すブール値を返します。この方法は ES2016 で導入されました。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

このメソッドの 2 番目のパラメータは検索の開始位置を示し、デフォルトは「0」です。 2 番目のパラメータが負の数値の場合、逆数の位置が配列の長さより大きい場合 (たとえば、2 番目のパラメータが -4 ですが、配列の長さが 3 である場合) はリセットされます。 0スタートへ。

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

このメソッドが存在する前は、通常、配列の indexOf メソッドを使用して、配列に特定の値が含まれているかどうかをチェックしていました。

if (arr.indexOf(el) !== -1) {
  // ...
}

indexOf メソッドには 2 つの欠点があります。まず、このメソッドの意味は、パラメーター値の最初の出現位置を見つけることであるため、それが -1 と等しくないかどうかを比較する必要があります。直感的に表現できるほど。 2 つ目は、判定に内部的に厳密等価演算子 (===) を使用しているため、NaN の誤判定につながります。

[NaN].indexOf(NaN)
// -1

includesは別の判定アルゴリズムを使用しているため、そのような問題はありません。

[NaN].includes(NaN)
// true

次のコードは、現在の環境がこのメソッドをサポートしているかどうかを確認するために使用されます。サポートしていない場合は、単純な代替バージョンをデプロイします。

const contains = (() =>
  Array.prototype.includes
    ? (arr, value) => arr.includes(value)
    : (arr, value) => arr.some(el => el === value)
)();
contains(['foo', 'bar'], 'baz'); // => false

さらに、Map および Set データ構造には has メソッドがあり、これは includes と区別する必要があります。

  • Map 構造の has メソッドは、Map.prototype.has(key)WeakMap.prototype.has(key)Reflect.has(target, propertyKey) などのキー名を検索するために使用されます。
  • Set 構造体の has メソッドは、Set.prototype.has(value)WeakSet.prototype.has(value) などの値を検索するために使用されます。

インスタンスメソッド: flat(),flatMap()

配列のメンバーが依然として配列である場合があり、入れ子になった配列を 1 次元配列に「平坦化」するために Array.prototype. flat() が使用されます。このメソッドは新しい配列を返しますが、元のデータには影響しません。

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]

上記のコードでは、元の配列のメンバー内に配列があり、「 flat() 」メソッドが部分配列のメンバーを取り出して元の位置に追加します。

flat() は、デフォルトでは 1 つのレイヤーのみを「フラット化」します。複数レベルのネストされた配列を「フラット化」したい場合は、 flat() メソッドのパラメーターを整数として記述し、そのレイヤーの数を示します。平坦化するレイヤーの数。デフォルトは 1 です。

[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]

[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]

上記のコードでは、 flat() のパラメータは 2 で、これは 2 レベルのネストされた配列を「平坦化」することを意味します。

ネストのレベルに関係なく、それを 1 次元配列に変換したい場合は、パラメーターとして Infinity キーワードを使用できます。

[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]

元の配列にギャップがある場合、「 flat() 」メソッドはギャップをスキップします。

[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]

flatMap() メソッドは、元の配列の各メンバーに対して関数を実行し (Array.prototype.map() を実行するのと同等)、その後、戻り値で構成される配列に対して flat() メソッドを実行します。 。このメソッドは、元の配列を変更せずに新しい配列を返します。

// [[2, 4], [3, 6], [4, 8]]. flat() と同等
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]

flatMap() は配列の 1 レベルのみを展開できます。

// [[[2]]、[[4]]、[[6]]、[[8]]]. flat() と同等
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]

上記のコードでは、トラバーサル関数は 2 層配列を返しますが、デフォルトでは 1 層しか展開できないため、「 flatMap() 」は引き続きネストされた配列を返します。

flatMap() メソッドのパラメータはトラバーサル関数で、現在の配列メンバー、現在の配列メンバーの位置 (0 から始まる)、および元の配列の 3 つのパラメータを受け入れることができます。

arr.flatMap(function callback(currentValue[, index[, array]]) {
  // ...
}[, thisArg])

flatMap() メソッドには、トラバーサル関数で this をバインドするために使用される 2 番目のパラメーターを持つこともできます。

インスタンスメソッド: at()

長い間、javascript は配列の負のインデックス付けをサポートしていませんでした。配列の最後のメンバーを参照したい場合、arr[-1] を記述することはできず、arr[arr.length - 1] のみを使用できます。 ]

これは、javascript 言語では角括弧演算子 [] が配列だけでなくオブジェクトにも使用されるためです。オブジェクトの場合、キー名は角括弧内にあります。たとえば、「obj[1]」はキー名文字列「1」のキーを指します。同様に、「obj[-1]」はキー名文字列を指します。 「-1」キー。 javascript 配列は特殊なオブジェクトであるため、角括弧内の負の数値には他の意味がありません。つまり、負のインデックスをサポートする新しい構文を追加することはできません。

この問題を解決するために、ES2022 は、配列インスタンスに at() メソッドを追加し、パラメータとして整数を受け入れます。対応する位置メンバーを返し、負のインデックス付けをサポートします。このメソッドは配列だけでなく、文字列や型付き配列 (TypedArray) にも使用できます。

const arr = [5, 12, 8, 130, 44];
arr.at(2) // 8
arr.at(-2) // 130

パラメータの位置が配列の範囲を超える場合、at()unknown を返します。

const sentence = 'This is a sample sentence';

sentence.at(0); // 'T'
sentence.at(-1); // 'e'

sentence.at(-100) // undefined
sentence.at(100) // undefined

インスタンスメソッド: toReversed()、toSorted()、toSpliced()、with()

push()pop()shift()unshift() など、配列の従来のメソッドの多くは元の配列を変更します。これらのメソッドが配列に対して呼び出されている限り、その値は変化します。 ES2023 では、配列を操作する場合、元の配列は変更されませんが、元の配列のコピーが変更される 4 つの新しいメソッドが導入されています。戻ってきました。

  • Array.prototype.toReversed() -> Array
  • Array.prototype.toSorted(compareFn) -> Array
  • Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
  • Array.prototype.with(index, value) -> Array

これらはそれぞれ、配列の元のメソッドに対応します。

  • toReversed() は、配列メンバーの位置を反転するために使用される reverse() に対応します。
  • toSorted() は、配列メンバーをソートするために使用される sort() に対応します。
  • toSpliced() は、指定された数のメンバーを削除し、指定された位置に新しいメンバーを挿入するために使用される splice() に対応します。
  • with(index, value) は、指定された位置のメンバーを新しい値に置き換えるのに使用される splice(index, 1, value) に対応します。

上記は、これら 4 つの新しいメソッドに対応する元のメソッドです。意味と使用法はまったく同じです。唯一の違いは、元の配列は変更されず、操作後に元の配列のコピーが返されることです。

以下に例を示します。

const sequence = [1, 2, 3];
sequence.toReversed() // [3, 2, 1]
sequence // [1, 2, 3]

const outOfOrder = [3, 1, 2];
outOfOrder.toSorted() // [1, 2, 3]
outOfOrder // [3, 1, 2]

const array = [1, 2, 3, 4];
array.toSpliced(1, 2, 5, 6, 7) // [1, 5, 6, 7, 4]
array // [1, 2, 3, 4]

const correctionNeeded = [1, 1, 3];
correctionNeeded.with(1, 2) // [1, 2, 3]
correctionNeeded // [1, 1, 3]

インスタンスメソッド: group(),groupToMap()

配列メンバーのグループ化は、「GROUP BY」句を使用した SQL や MapReduce メソッドを使用した関数プログラミングなどの一般的な要件です。現在、提案 があり、配列インスタンス メソッド group()groupToMap() を javascript に追加します。これは、以下に従って使用できます。グループ化関数を実行し、配列メンバーをグループ化します。

group() のパラメータはグループ化関数で、元の配列の各メンバーがこの関数を順番に実行して、どのグループに属するかを決定します。

const array = [1, 2, 3, 4, 5];

array.group((num, index, array) => {
  return num % 2 === 0 ? 'even': 'odd';
});
// { odd: [1, 3, 5], even: [2, 4] }

group() のグループ化関数は、配列の現在のメンバー、メンバーの位置番号、および元の配列 (上記の例は numindex、および array) の 3 つのパラメーターを受け入れることができます。 )。グループ化関数の戻り値は、グループ化後のグループ名として文字列 (または自動的に文字列に変換できる) である必要があります。

group() の戻り値はオブジェクトです。オブジェクトのキー名は各グループのグループ名、つまりグループ化関数によって返される各文字列です (上記の例は evenodd)。 ; オブジェクトのキー値は配列であり、現在のキー名を生成した元の配列メンバーがすべて含まれます。

別の例を示します。

[6.1, 4.2, 6.3].group(Math.floor)
// { '4': [4.2], '6': [6.1, 6.3] }

上記の例では、元の配列をグループ化するためのグループ化関数として Math.floor が使用されています。戻り値は本来は数値ですが、グループのグループ名として自動的に文字列に変換されます。元の配列のメンバーは、グループ化関数の演算結果に従って、対応するグループに入ります。

group() は 2 番目のパラメータとしてオブジェクトを受け入れることもできます。このオブジェクトはグループ化関数(最初のパラメータ)の this をバインドしますが、グループ化関数がアロー関数の場合、アロー関数内の this が固定化されているため、オブジェクトは無効になります。

groupToMap() の機能と使用法は group() とまったく同じです。唯一の違いは、戻り値がオブジェクトではなく Map 構造体であることです。 Map 構造のキー名にはさまざまな値を指定できるため、グループ化関数によってどのような値が返されたとしても、その値はグループ名 (Map 構造のキー名) として直接使用され、強制されることはありません。文字列に変換されます。これは、グループ化関数の戻り値がオブジェクトである場合に特に便利です。

const array = [1, 2, 3, 4, 5];

const odd  = { odd: true };
const even = { even: true };
array.groupToMap((num, index, array) => {
  return num % 2 === 0 ? even: odd;
});
//  Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }

上の例は Map 構造を返します。そのキー名は、グループ化関数によって返される 2 つのオブジェクト oddeven です。

つまり、文字列でグループ化するには group() を使用し、オブジェクトごとにグループ化するには groupToMap() を使用します。

配列内の空の位置

配列の空の位置は、配列の特定の位置に値がないことを意味します。たとえば、Array() コンストラクターによって返される配列はすべて空です。

Array(3) // [, , ,]

上記のコードでは、Array(3) は 3 つの空のビットを含む配列を返します。

空の位置は「未定義」ではないことに注意してください。ある位置の値は「未定義」に等しく、まだ値があります。空のビットには値がありません。これを「in」演算子で説明できます。

0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false

上記のコードは、最初の配列の位置 0 には値があり、2 番目の配列の位置 0 には値がないことを示しています。

ES5 のギャップの処理はすでに非常に一貫性がなく、ほとんどの場合ギャップは無視されます。

  • forEach()filter()reduce()every()、および some() はすべてギャップをスキップします。
  • map() は空のビットをスキップしますが、値は保持します
  • join()toString() はギャップを unknown として扱いますが、 unknownnull は空の文字列として扱われます。
// forEach メソッド
[,'a'].forEach((x,i) => console.log(i)); // 1

// フィルターメソッド
['a',,'b'].filter(x => true) // ['a','b']

// すべてのメソッド
[,'a'].every(x => x==='a') // true

// メソッドを減らす
[1,,2].reduce((x,y) => x+y) // 3

// いくつかのメソッド
[,'a'].some(x => x !== 'a') // false

// マップメソッド
[,'a'].map(x => 1) // [,1]

// 結合メソッド
[,'a',unknown,null].join('#') // "#a##"

// toString メソッド
[,'a',unknown,null].toString() // ",a,,"

ES6 はギャップを明示的に「未定義」に変換します。

「Array.from()」メソッドは、配列内の空のビットを「未定義」に変換します。つまり、このメソッドは空のビットを無視しません。

Array.from(['a',,'b'])
// [ "a", undefined, "b" ]

スプレッド演算子 (「...」) も空のビットを「未定義」に変換します。

[...['a',,'b']]
// [ "a", undefined, "b" ]

copyWithin() は隙間もコピーします。

[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]

fill() はギャップを通常の配列位置として扱います。

new Array(3).fill('a') // ["a","a","a"]

「for...of」ループもギャップを繰り返します。

let arr = [, ,];
for (let i of arr) {
  console.log(1);
}
// 1
// 1

上記のコードでは、配列 arr に 2 つの空の位置があり、for...of はそれらを無視しません。 map() メソッドに変更してトラバースすると、空のビットはスキップされます。

entries()keys()values()find()、および findIndex() はギャップを 未定義 として扱います。

// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]

// keys()
[...[,'a'].keys()] // [0,1]

// values()
[...[,'a'].values()] // [undefined,"a"]

// find()
[,'a'].find(x => true) // undefined

// findIndex()
[,'a'].findIndex(x => true) // 0

ギャップの処理ルールは非常に一貫性がないため、ギャップを避けることをお勧めします。

Array.prototype.sort() のソートの安定性

ソートの安定性 (安定したソート) は、ソート アルゴリズムの重要な属性です。これは、同じソート キーワードを持つアイテムの順序がソートの前後で変わらないことを意味します。

const arr = [
  'peach',
  'straw',
  'apple',
  'spork'
];

const stableSorting = (s1, s2) => {
  if (s1[0] < s2[0]) return -1;
  return 1;
};

arr.sort(stableSorting)
// ["apple", "peach", "straw", "spork"]

上記のコードは、配列 arr を最初の文字に従ってソートします。ソート結果では、「straw」が「spork」より前にあり、元の順序と一致しているため、ソートアルゴリズム「stableSorting」は安定したソートです。

const unstableSorting = (s1, s2) => {
  if (s1[0] <= s2[0]) return -1;
  return 1;
};

arr.sort(unstableSorting)
// ["apple", "peach", "spork", "straw"]

上記のコードでは、ソート結果が straw よりも spork となり、本来の順序とは逆になっているため、ソートアルゴリズム unstableSorting は不安定です。

一般的なソートアルゴリズムのうち、挿入ソート、マージソート、バブルソートなどは安定していますが、ヒープソート、クイックソートなどは不安定です。不安定な並べ替えの主な欠点は、複数の並べ替えで問題が発生する可能性があることです。姓と名のリストがあり、「姓が主キーワード、名が副キーワード」に従ってそれらを並べ替える必要があるとします。開発者は、名、姓の順に並べ替えることができます。並べ替えアルゴリズムが安定している場合、「姓が最初、名前が最初」の並べ替え効果を実現できます。不安定な場合はそうではありません。

以前の ECMAScript では、「Array.prototype.sort()」のデフォルトの並べ替えアルゴリズムが安定しているかどうかが指定されておらず、決定をブラウザーに任せていたため、一部の実装が不安定になりました。 ES2019 では、「Array.prototype.sort()」のデフォルトのソートアルゴリズムが安定している必要があると明確に述べられています。この規定は実装されており、すべての主要な javascript 実装のデフォルトの並べ替えアルゴリズムは安定しています。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0