はじめに
この記事は初心者向けのものでも、実用向けのものでもありません!
思いっきり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への理解を深めるためのものに留めておくべきでしょう。
以上です。何か少しでもおかしなところがあれば、これまでして頂いたように、遠慮なく指摘してほしいです。
お互いのスキルを高め合っていきましょう!