166
101

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-06-25

業務中ワイ

ワイ「なあ、ハスケル子ちゃん」

ハスケル子「はい」

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

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

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

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

ハスケル子「
haskellko.jpeg

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

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

ハスケル子「(す、すいません!)」

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

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

mapメソッドとは

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

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

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

const nodesArray = Array.from(nodes);

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

const nodesHeightArray = nodesArray.map(getHeight);

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

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

ハスケル子「はい」

const getHeight = element => element.clientHeight;

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

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

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

[24, 48, 72, 120, 24, 72, 48]

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

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

ワイの気になったこと

ワイ「でもハスケル子ちゃん」

const getHeight = element => element.clientHeight;

ワイ「↑このgetHeightの引数であるelementどこで渡してんの?

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

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

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

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

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

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

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

ワイ「お、おう・・・」

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

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

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

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

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

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

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

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

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

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

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メソッド試してみるで!」

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

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

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

ハスケル子「いい感じです」
ハスケル子「実際のmapメソッドは**もっと複雑なアルゴリズム**ですけど」
ハスケル子「イメージ的はこんな感じです」

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

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

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

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

コールバック関数について

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

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

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

Object.definePropertyについて

ワイ「あと、Object.definePropertyすごいな」

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

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

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

社長「(ハスケル子ちゃん、そこはちゃんと止めてや・・・!)」

そうこうしてるうちに18時

ワイ「やば、ワイがコード読めへんせいでもう定時やないかい」
ワイ「コーディング全然進んでへん・・・」

Riot兄さん「俺に任しとけ

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

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

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

ハスケル子「物理界王拳・・・?(兄貴、すごい・・・///)」

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

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

〜おしまい〜

追記

ワイ「っていうかホンマの界王拳も物理やろ!

  1. 実際にはArray.prototypeのメソッドとして定義されてるけど、きっとやめ太郎さん分からないから・・・

  2. ノードリストですね。

  3. グローバルなオブジェクトに手をつけることになるので、むやみにやらない方がいいと思います。

  4. 実際には残業はほぼありません。(フロントエンドチーム以外は知りません)

166
101
19

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
166
101

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?