Array.mapが分からないからArray.mapもどきを作ってみた

## 業務中ワイ
ワイ「なあ、ハスケル子ちゃん」

ハスケル子「はい」

ワイ「今、ハスケル子ちゃんの書いたJSのコードを見てたんやけど」
ワイ「これどういう意味？」

```javascript
const nodes = document.querySelectorAll(".list > li");
const nodesArray = Array.from(nodes);
const nodesHeightArray = nodesArray.map(getHeight);
```

ハスケル子「えっと、うーん・・・」
ハスケル子「（見ての通りなんだけど、逆にどこが分からないんだろう・・・）」
ハスケル子「どこが分かりにくかったですか？」

ワイ「この`nodesArray.map`いうメソッドは何なん？」

ハスケル子「**？**」
![haskellko.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/c549d917-2af0-b867-1459-a412a4f59951.jpeg)

ハスケル子「**なんでそんなことが分からないんだろう・・・**」

ワイ「お、おい・・・**心の声が出てもうてるで・・・**」

ハスケル子「（す、すいません！）」

ワイ「**なんで謝罪は心の声になっとんねん**」
ワイ「**どんな忖度や**」
ワイ「**判断基準どうなってんねん**」

ハスケル子「ごめんなさい」
ハスケル子「お詫びに`map`メソッドについて説明しますね」

## `map`メソッドとは
ハスケル子「`map`メソッドは、配列が持ってるメソッド[^1]です」
ハスケル子「ある配列を元に、新しく**別の配列を作ることができる**メソッドですね」
ハスケル子「さっきのコードでいうと───」

```javascript
const nodes = document.querySelectorAll(".list > li");
```

ハスケル子「↑まず`querySelectorAll`で、いくつかの`li`要素を取得して」
ハスケル子「それを`nodes`とします」

```javascript
const nodesArray = Array.from(nodes);
```

ハスケル子「↑次に`nodes`を`Array.from`メソッドで**配列に変換**します」
ハスケル子「`nodes`は**配列みたいだけど配列じゃない[^2]**ですからね」
ハスケル子「そして`nodes`から作った配列`nodesArray`の───」

```javascript
const nodesHeightArray = nodesArray.map(getHeight);
```

ハスケル子「───`map`メソッドを実行します」
ハスケル子「`nodesArray`の各要素に対して**何らかの処理**を施して」
ハスケル子「新しく`nodesHeightArray`という配列を作る、って感じです」
ハスケル子「その**何らかの処理**というのを、**関数として渡します**」

ワイ「なるほどな」
ワイ「`getHeight`が関数なんやね」

ハスケル子「はい」

```javascript
const getHeight = element => element.clientHeight;
```

ハスケル子「`getHeight`関数の中身は↑これです」

ワイ「`element`という引数を1つ受け取って`element.clientHeight`を返すということは」
ワイ「要素の高さを返す関数やな」

ハスケル子「はい」
ハスケル子「さっきのコードを実行すると、最終的に`nodesHeightArray`には」

```javascript
[24, 48, 72, 120, 24, 72, 48]
```

ハスケル子「↑という配列が入ることになります」

ワイ「ほえ〜、DOM要素のリストを元に、各要素の高さを集めた配列を作れるんか」
ワイ「たまに便利そうやな」

## ワイの気になったこと
ワイ「でもハスケル子ちゃん」

```javascript
const getHeight = element => element.clientHeight;
```

ワイ「↑この`getHeight`の引数である`element`は**どこで渡してんの？**」

ハスケル子「どこで渡すっていうか、勝手に入ってくるんです」

ワイ「え・・・勝手に入ってくる・・・？」
ワイ「**空き巣みたいに・・・？**」

ハスケル子「やめ太郎さんも、自分で`map`メソッドを実装してみたら分かりますよ」
ハスケル子「やってみましょう」

```javascript
Object.defineProperty(Array.prototype, "myMap", {
  value: function(callback) {
    /* ここに処理内容を書く */
  }
});
```

ハスケル子「↑こうやって」
ハスケル子「`Object.defineProperty`メソッドを使うことで」
ハスケル子「配列に`myMap`というメソッドを追加できるんです[^3]」

ワイ「おお、そんな機能があるんか」
ワイ「ほなちょっと挑戦してみるわ」
ワイ「でも・・・この`callback`いう引数はどこから入ってくんの」

ハスケル子「だから・・・」
ハスケル子「その辺を理解するために今からコードを書いてもらうんです」
ハスケル子「**再帰的な質問をしないでください**」
ハスケル子「もうとりあえず**空き巣が勝手に入ってくる**とでも思っておいてください」

ワイ「お、おう・・・」

ハスケル子「配列の`map`メソッドには、関数を1つ渡して実行しましたよね？」

ワイ「ああ、さっきしてたな」

ハスケル子「そのとき`map`メソッドに渡した関数が」
ハスケル子「今から書く関数の`callback`という引数として入ってくる・・・」
ハスケル子「そんなイメージで`myMap`メソッドも実装してみてください」

ワイ「なるほど、やってみるで」

```javascript
Object.defineProperty(Array.prototype, "myMap", {
  value: function(callback) {
    const resultArray = []; // まず空の配列を作成
    /* ここで配列に色々する */
    return resultArray; // その配列を戻り値として返す
  }
});
```

ワイ「まずは↑こんな感じやな」
ワイ「空の配列を作って、なんか色々して、最終的にその配列を返す、と」

ハスケル子「いいですね」

ワイ「ほんで、元々の配列の各要素に対して**何らかの処理**をしていけばいいんやな」
ワイ「そうか」
ワイ「その**何らかの処理**が、`callback`として受け取った関数に入ってるという**テイ**で考えればいいんやな」

ハスケル子「そうです」
ハスケル子「ちなみに今書いてる関数の中では`this`って書けばその配列自身にアクセスできますよ」

ワイ「なるほど、`this`が配列自身やな」
ワイ「ほな、配列の分だけ`for文`で回して───」

```javascript
for (let i = 0; i < this.length; i++) {
  const newElement = callback(this[i]); // i番目の要素にcallbackを適用。
  resultArray.push(newElement); // それを「戻り値として返す配列」に追加。
}
return resultArray;
```

ワイ「↑こうやな！」

ワイ「`this[i]`、つまり配列の**`i`番目の要素**に対して**`callback`関数を実行**して」
ワイ「それを`newElement`として配列に`push`する」
ワイ「そして出来上がった配列`resultArray`を戻り値として返す」

ハスケル子「いいですね！」

ワイ「よっしゃ、じゃあワイの`myMap`メソッド試してみるで！」

```javascript
const nodesHeightArray = nodesArray.myMap(getHeight);
console.log(nodesHeightArray);

// コンソール結果: [24, 48, 72, 120, 24, 72, 48]
```

ワイ「おお、さっきの`map`メソッドと同じ結果や！」

ハスケル子「いい感じです」
ハスケル子「実際の`map`メソッドは**[もっと複雑なアルゴリズム](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Polyfill)**ですけど」
ハスケル子「イメージ的はこんな感じです」

ワイ「なるほどな〜」
ワイ「`map`メソッドに対して渡した`getHeight`いう関数を」
ワイ「`map`メソッドの中で、配列の各要素に対して実行してくれてたんやな」
ワイ「そのときに`this[i]`的な感じで」
ワイ「`callback`関数の引数として、元の配列の各要素を渡してくれてたんやな」
ワイ「せやから引数が勝手に入って来てたんか〜」
ワイ「謎が解けましたわ〜」
ワイ「ありがとう、ハスケル子ちゃん」

ハスケル子「実際の`map`メソッドは、`callback`関数を実行するときに`this[i]`だけじゃなく」
ハスケル子「`i`と`this`も渡してくれるので、さらに色々できますよ」

ワイ「なるほどな」
ワイ「第二引数に**今何番目の要素か**を表すインデックス」
ワイ「第三引数には**配列自身**が入ってくると」
ワイ「工夫次第で色々できそうやな」
ワイ「`array[i - 1]`で一つ前の要素を見たりとかな」

ハスケル子「そうですそうです」

## コールバック関数について
ワイ「今みたいな感じで、**関数を渡して使う**タイプの関数を」
ワイ「自分で作ってみてもオモロそうやな〜」
ワイ「例えば共通化できそうな処理があったとして」
ワイ「ほとんどの処理を関数として共通化できるけど」
ワイ「最後にちょっとだけ挙動を変えたい」
ワイ「しかも何パターンかの挙動があるから、引数で何かパラメータを受け取ってやる方法やと」
ワイ「if文が増えすぎて見通しが悪くなる・・・そんな時には」
ワイ「**関数の最後にこの処理をやってくれい！**」
ワイ「っていう処理の内容を、**コールバック関数として**受け取って」
ワイ「最後に実行してやればええんやな〜」
ワイ「そうすれば」
ワイ「**同じ関数を使うけど、一部だけ自由に挙動を変える**ことが出来んねやな」

ハスケル子「だんだん中級者っぽくなってきましたね」

ワイ「（いや、君よりだいぶJS歴長いねんけどな・・・）」

## `Object.defineProperty`について
ワイ「あと、`Object.defineProperty`すごいな」

ハスケル子「**Vue.js**なんかも、この`Object.defineProperty`で」
ハスケル子「`Array`の`push`メソッド等を上書きすることで」
ハスケル子「**配列に要素を追加しただけ**で**DOMが更新される**ようにしてるんですよ」

ワイ「せやろな〜（**まじか！**）」
ワイ「よっしゃ、ワイも明日から**配列にメソッド追加しまくる**で〜！」
ワイ「あ、メソッドの**上書きもできる**んやったな！」

ハスケル子「**レッツ、プロトタイプ汚染っ！**」

社長「（ハスケル子ちゃん、**そこはちゃんと止めてや・・・！**）」

## そうこうしてるうちに18時
ワイ「やば、ワイがコード読めへんせいでもう定時やないかい」
ワイ「コーディング全然進んでへん・・・」

Riot兄さん「**俺に任しとけ**」

ワイ「あ、兄貴・・・！」

Riot兄さん「俺が**界王拳2倍**でコーディングして、なんとかしたるわ」
Riot兄さん「よっしゃ、今日から**毎日7時間残業**するで〜！」

社長「おっ、兄貴の**物理界王拳**やな！」

ハスケル子「**物理界王拳**・・・？（兄貴、すごい・・・///）」

社長「せや」
社長「ただし物理界王拳は**3倍まで**しか使えへん」
社長「**24時間超えてしまう**からな」

ワイ「**物理界王拳**て」
ワイ「**ただのブラック会社[^4]やないかい！**」

**〜おしまい〜**

## 追記
ワイ「っていうか**ホンマの界王拳も物理やろ！**」


[^1]: 実際には`Array.prototype`のメソッドとして定義されてるけど、きっとやめ太郎さん分からないから・・・
[^2]: ノードリストですね。
[^3]: グローバルなオブジェクトに手をつけることになるので、むやみにやらない方がいいと思います。
[^4]: 実際には残業はほぼありません。（フロントエンドチーム以外は知りません）