配列操作のすゝめ (map,forEachとかが便利すぎる話)

# はじめに

**この記事は初心者向けのものでも、実用向けのものでもありません！**
思いっきりJavaScriptの仕様で遊ぶ記事であって、詳しい解説がされていないものもあります。
読んでいてわからないことがあったら、**[MDN](https://developer.mozilla.org/ja/docs/Glossary/JavaScript)などで調べてみてください。**

# JavaScriptの型標準メソッドが便利

`Array`,`Object`のメソッドの`map()`, `forEach()`が、ショートコーディング好きの私にピッタリだったので、少しマニアック(で実用には向かない1)なコードを紹介します。

### ちなみにどんなメソッド？

それぞれ動きはほとんど同じですが、返り値がちょっと違います。

|メソッド|`forEach()`|`map()`|`filter()`|`some()`|
|:-:|:-:|:-:|:-:|:-:|
|引数|callback: function|callback: function|callback: function|callback: function|
|callbackに渡される引数|要素自身、要素のインデックス|左記同様|左記同様|左記同様|
|処理|全ての要素それぞれを引数にcallbackを呼ぶ|左記同様|左記同様|左記同様|
|**返り値**|**undefined**|**callbackの返り値からなる新しい配列**|**callbackの返り値がtruthyだったものだけを含む、新しい配列**|**callbackの返り値のどれかがtruthyだった場合にtrue、そうでない場合にfalseのboolean**|
|処理速度|速い|遅い|- - - - - -|- - - - - -|

何をしたいかによって使い分けましょう。そうすることで高速化が狙えますし、読み手にも優しくなります。

# 具体例

## idをキーに、idのあるDOM要素だけを含むオブジェクトを生成

わかりやすいようにインデントすると、こんな感じです。

```js
let elems = new (
  function() {
    [...document.querySelectorAll("*")]
      .filter (element => element.id)
      .forEach(element => this[element.id] = element)
  }
)();
```
###仕組み

まず、`document.querySelectorAll("*")`で、ページ上の全ての要素を取得します。

次に、**スプレッド構文**の裏技を使って、`arrayLikeObject`から、ちゃんとした配列に変換します。
`arrayLikeObject`は、配列に似た**ただのオブジェクト**なので、**`.map()`も`.forEach()`も存在しない**ので、ここで変換する必要があります。

追記 (2020/11/20)
ちなみに、`Function.prototype.apply()`を使って無理やりメソッドを呼び出す方法もありますが、当時は知りませんでした。
しかし、どちらにせよ冗長なので採用しないことにします。

```js
let array;

array = [...arrayLikeObject];

// 別の書き方
array = Array(...arrayLikeObject);
array = Array.from(arrayLikeObject);
array = new Array(...arrayLikeObject);
```

そして、idを持っているエレメントだけ取り出すために、`filter()`を使います。
それを`this`に書き出したら終わりです。

### 裏話

このコードでは、結果を`this`に書き出すために**`new`と[無名関数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/function)を組み合わせています**。
[無名関数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/function)を`new`しても、ちゃんとコンストラクタとして扱ってくれるので、`this`を返り値のように扱えます。これにより、`return`するためのオブジェクトを格納する**変数を、いちいち宣言しなくてもよい**のです。

ここで、「アロー関数の方が短いのに、なんで使わないの？」と思った人もいると思いますが、ちゃんと理由があります。
実は、アロー関数から`this`を参照すると、**その関数が所属しているオブジェクト**ではなく、**グローバルの`this`が返ってくる**のです。
加えて、アロー関数を`new`することも**できません**。コンストラクタとして扱ってくれず、エラーになります。
なので、`function() {}`式を使わなければなりません。

ちなみにminバージョンはこちら。

```js
let elems=new(function(){[...document.querySelectorAll("*")].filter(e=>e.id).forEach(e=>this[e.id]=e)})();
```

うーん、短い、最高。

# 小ネタ

## 配列から範囲を指定して取り出し

引数はインデックスの下限値、上限値です。

```js
Array.prototype.range = function(min, max) {
  return this.filter(
    (element, index) =>
      (min || 0)           <= index &&
      (max || this.length) >= index
  )
};

const hogeArray = ["foo", "bar", "baz", "qux", "quux"]
console.log( hogeArray.range(2)    ); // ["baz", "qux", "quux"]
console.log( hogeArray.range(1, 3) ); // ["bar", "baz", "qux"]
```

インデックスを不等号で評価(`min`以上、`max`以下)して、`filter()`に渡しています。

min版

```js

Array.prototype.range=function(n,x){return this.filter((e,i)=>(n||0)<=i&&(x||this.length)>=i)};
```

## 配列反転

```js:.js
Array.prototype.flip = function() {
  return this.map((element, index) => 
    this[this.length-++index]
  )
};

console.log( ["foo", "bar", "baz"].flip() ); // ["baz", "bar", "foo"]
```

thisArgが`["foo", "bar", "baz"]`の場合の動作を表にしてみます。

|index|0|1|2|
|:-:|:-:|:-:|:-:|
|`this.length - index`|`3-0 == 3`|`3-1 == 2`|`3-2 == 1`|
|`this.length - ++index`|`3-(++0) == 3-1 == 2`|`3-(++1) == 3-2 == 1`|`3-(++2) == 3-3 == 0`|
|`this[index]`|`this[0] == "foo"`|`this[1] == "bar"`|`this[2] == "baz"`|
|`this[this.length-++index]`|`this[2] == "baz"`|`this[1] == "bar"`|`this[0] == "foo"`|

min版

```js:.min.js
Array.prototype.flip=function(){return this.map((e,i)=>this[this.length-++i])};
```

# あとがき

いかがだったでしょうか。

この記事では、いろいろな面白い関数を紹介しました。
ですが、冒頭でも言った通り、これらは全く**実用的ではありません**。
コメントで指摘して頂いた通り、標準オブジェクトに新しい関数を追加するこのテクニックは、プロトタイプ汚染とも呼ばれています。
汚染という言葉が使われている通り、この操作は[様々な問題](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/proto)を引き起こす可能性があり、非推奨となっています。
なので、本番環境で使うことは**お勧めしません**。この記事で知った内容は、JavaScriptへの理解を深めるためのものに留めておくべきでしょう。

以上です。何か少しでもおかしなところがあれば、これまでして頂いたように、遠慮なく指摘してほしいです。
お互いのスキルを高め合っていきましょう！