LoginSignup
2
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-05

はじめに

この記事は初心者向けのものでも、実用向けのものでもありません!
思いっきりJavaScriptの仕様で遊ぶ記事であって、詳しい解説がされていないものもあります。
読んでいてわからないことがあったら、MDNなどで調べてみてください。

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要素だけを含むオブジェクトを生成

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

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()を使って無理やりメソッドを呼び出す方法もありますが、当時は知りませんでした。
しかし、どちらにせよ冗長なので採用しないことにします。

let array;

array = [...arrayLikeObject];

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

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

裏話

このコードでは、結果をthisに書き出すためにnew無名関数を組み合わせています
無名関数newしても、ちゃんとコンストラクタとして扱ってくれるので、thisを返り値のように扱えます。これにより、returnするためのオブジェクトを格納する変数を、いちいち宣言しなくてもよいのです。

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

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

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

うーん、短い、最高。

小ネタ

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

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

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版


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

配列反転

.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版

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

あとがき

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

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

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

2
2
7

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
2
2