JavaScriptの長い関数名を短い別名にする方法
概要
JavaScriptの長い関数名が煩わしくなることがままあります。
他の言語ではそうないのですが、なぜだかJavaScriptだけ。
理由はjQueryなどで短い名前に慣れてしまったことなんだろうけれども単純に
const $ = document.querySelector;
としたところで、

「Uncaught TypeError: Illegal invocation」というエラーが出てしまいます。
これはいったいどういうことなんでしょうか。
経緯
今週は時間がないので次作業していることから小ネタを記事化。
ターゲット
- JavaScriptの長い関数名を
$などに短縮したい人
ゴール
- JavaScriptの癖がわかるようになる
正しい方法の例
答えはいろいろあるでしょうが、簡単な解決方法はたぶん2つ。
const $ = (parent, selector) => parent.querySelector(selector);
引数を要求する方法と
const $ = document.querySelector.bind(document);
Function.prototype.bind関数を使う方法です。
誤った例で実現できない理由
JavaScriptのthisの扱いが原因です。
const $ = document.querySelector;
例文にthisは登場していませんが、関数の中、特にprototype関数は内部的にthisを使用しています。
querySelectorはdocumentやElementのprototype関数です。
普通の使い方をしている場合は、それぞれの.の左側をthisとして参照することとなります。
let element1 = document.querySelector('body');
let element2 = element1.querySelector('div');
たとえば例示の場合、element1のquerySelectorのthisはdocument。
element2のquerySelectorのthisは、.の左側なのでelement1です。
これはthisがprototype宣言された関数や

Classのメソッドであれば

それぞれの親であるオブジェクトが参照されるからです。
(prototype宣言とClassのメソッド宣言は互換性のある宣言なので、同じような結果になるのは当たり前ですね)
しかし変数に関数をいれるとなると、このthisの事情が変わります。
たとえばprototype宣言した関数をそのまま変数に入れようとすると、thisの結果がかわってしまったように見えます。

これは、prototype宣言した関数がオブジェクト参照ごと変数に代入されるわけではなく、実際にはprototype宣言されている関数だけを変数に入れているからです。
関数の{}の中に書かれているプログラム文がコピーアンドペーストされた状態と思っていただいた方がとても分かりやすいでしょう。
この場合、prototype宣言やClassのメソッドではない通常の関数と同じような扱いになります。
通常の関数は、宣言するとJavaScriptの最上位オブジェクトであるwindowに登録されます。

そのため、関数でthisを使用しようとするとthisは受け継がれずにwindowを参照してしまい、誤った例ではこの現象が発生してしまっているんですね。

したがってthisを正常に参照するように関数を作る必要があるので、
const $ = (parent, selector) => parent.querySelector(selector);
thisの大本となるparentを引数で持ってくる方法か、
const $ = document.querySelector.bind(document);
関数のthisを変更することができるFunction.prototype.bind関数を使えば問題が解決できます。
Discussion