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