今回はjavascriptで知ってる人にとっては当たり前だけど、そうでない人は全然知らないようなテクニックをまとめていきます。
globalを汚さない無名関数スコープ
javascriptの問題のひとつとしてスコープがよくとりあげられますが、ここではグローバルスコープを汚さない手法を紹介していきます。
悪い例
1 2 |
|
ちなみにglobalスコープとはwindowオブジェクトにプロパティを追加していくことと同義です。
1 2 3 |
|
解決するには下記のように無名関数を用います。
1 2 3 4 5 |
|
また、jQueryを使うときは、下記のように引数でjQueryを渡して、$という名前で引数として受け取ってあげると、$のconflict問題から開放されます。
というか$をグローバル参照するのはバグの元になるのでやめましょう。
1 2 3 4 5 |
|
ちなみに無名関数の書き方は他にもあります。気にしなきゃいけないほどの違いはないので好みで選んでよいでしょう。
1 2 3 4 5 6 7 |
|
※1 先頭のセミコロンはconcatした場合に、エラーになる可能性を排除するためです。
※2 callを使っているのは、通常の関数呼び出しだとstrictモードの場合に無名関数内部のthisがwindowオブジェクトにならないためです。
ArrayっぽいけどArrayじゃないObjectをArrayにする方法
document.getElementsByClassNameとかdocument.querySelectorAllとかで複数のDOMを取得したときの戻り値とか、argumentsプロパティは見た感じArrayっぽいのにArrayにはなってません。
その結果何がどうなるかというと、pushとかjoinとかArrayが持っているメソッドが使えず、非常に使いづらい状態です。
それを解消するための方法が下記のテクニックです。
1 2 3 4 5 |
|
ちょっと解説すると、まずArray型が持っているメソッドで、配列の一部を切り出すsliceメソッドがあります。
これは非破壊的メソッドで、このメソッドを実行したArrayはそのままで、実行結果を新しいArrayとして返却してくれます。
その結果引数を指定しなければ、Arrayをシャローコピー(1階層のみコピー)することができます。
さらにその特性を活かして、sliceをArrayのprototypeから直接呼び出し、callを使ってthisをArrayっぽいObjectで上書くことで完全なArray型にキャストすることができます。
関数のthisを指定したObjectでbind
当たり前に使われてますが、知らない人も結構多いかと思うので一応。applyとcallです。
javascriptのfunction型はprototypeにcallとapplyってメソッドを持っていて、呼び出したfunctionのthisを第一引数でbindすることができます。
1 2 3 4 5 6 |
|
jQueryでthisを参照するとDOMオブジェクトになってるのはこういう方法を使ってます。そのせいでthisってなんぞ?みたいなことになるわけですが。
callとapplyの違いは関数の引数の指定方法です。
callは第2引数が第1引数に、第3引数が第2引数に、といった具合に順番がひとつずつずれる方法になります。
applyは第2引数をArray指定する形になっていて、それが順番に引数になります。
apply使うと$.whenみたいなArrayの引数ではなく可変長な引数の関数が格段に使いやすくなるのでお勧めです。
可変長引数の扱い方
関数で可変長な引数に対応したい場合がたまにあります。その方が綺麗に見える場合だったり、プラグインを独自拡張したい場合だったり。
そんなときに使えるのargumentsプロパティです。
1 2 3 4 5 6 7 8 9 |
|
ただ、実際に可変長引数を使いたいと思った場合は、for文とかでまわして処理を行いたいことが多いです。
しかし、argumentsプロパティは配列っぽい要素であって配列ではありません。というわけで、上で書いた手法がよく使われることになります。
1 2 3 4 5 6 7 |
|
ちなみにargumentsプロパティは実装がヤバイらしく、あまり使用が推奨されていません。
ES6からは正式に可変長引数がサポートされるので将来的にはそっちを使うほうがよいです。
あとこれ必要ない場合に使うと可読性が著しく落ちるのでご利用は計画的に。
最後に、プラグイン内部で関数どおしが何やってるかわからないけど、一部の値をちょっと書き換えたり、取得したいって場合がまれによくあります。
そんなときは下記のようなテクニックが使えます。
1 2 3 4 5 6 7 8 9 |
|
このときはBackboneのajaxをカスタマイズして、エラーハンドリングを全処理で共通的に行うようなことをしたんですが、長くなるので今回は見送ります。
argumentsの隠された力
argumentsプロパティは上で書いたようによく引数取得で使われるわけですが、実は隠された力を持っています。
ただその力はあまりにチートで危険すぎるため、strictモードでは禁止されていますのであらかじめご了承ください。
arguments.callee
まず一つ目はarguments.calleeです。これはその関数自身のことです。無名関数だけど再帰処理がしたい場合とかに使えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
上記はObjectをdeepコピーする関数ですが、無名関数で作っているため再帰処理をする場合に自分自身を関数名で呼び出すことができません。
そのため、arguments.calleeで自分自身を呼んでいます。
もちろん名前付き関数として定義すれば問題なく動作しますが、関数名変えたりする場合に一箇所修正すればいいので個人的には好きな書き方です。
あと@azu_reさんからご指摘もらった件について追記します。
IE8を無視すれば名前付き関数式がサポートされてるので下記のような書き方が可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
arguments.callee.caller.arguments
次に紹介するのはarguments.callee.caller.argumentsです。
これはなんと呼び出し元の関数の引数を取得することができます。ちなみにarguments.callee.callerは呼び出しの関数が取得できます。
1 2 3 4 5 6 7 8 9 |
|
ただしこれをやってしまうと、可読性が著しく悪化するばかりか、関数の呼び出し順に過度に依存することになるので、非常事態以外には触るな危険です。
また、もちろんこれどんどん遡って行くことが可能で、一番最初の呼び出し元関数まで全部取得可能です。
私はBackbone.ajaxでBackbone.syncの引数の取得に使用しました。ごめんなさい。