はじめに
この記事は初心者向けのものでも、実用向けのものでもありません!
思いっきり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)};
配列反転
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版
Array.prototype.flip=function(){return this.map((e,i)=>this[this.length-++i])};
あとがき
いかがだったでしょうか。
この記事では、いろいろな面白い関数を紹介しました。
ですが、冒頭でも言った通り、これらは全く実用的ではありません。
コメントで指摘して頂いた通り、標準オブジェクトに新しい関数を追加するこのテクニックは、プロトタイプ汚染とも呼ばれています。
汚染という言葉が使われている通り、この操作は様々な問題を引き起こす可能性があり、非推奨となっています。
なので、本番環境で使うことはお勧めしません。この記事で知った内容は、JavaScriptへの理解を深めるためのものに留めておくべきでしょう。
以上です。何か少しでもおかしなところがあれば、これまでして頂いたように、遠慮なく指摘してほしいです。
お互いのスキルを高め合っていきましょう!