質問: 「`arguments.callee`は使うな!」のような記事を以前に読んだのですが(探しても見つかりませんでした)、どうして`arguments.callee`を使ってはいけないと言われているのでしょうか? また`arguments.callee`はstrictモードでも禁止されていますよね。どうして`arguments.callee`がいけないのか、いくつか問題のある例をご提示していただけると嬉しいです。 個人的には「再帰していることを明示」できる点で`arguments.callee`は良いとも思っています。
回答: MDNのarguments.calleeの解説の所に理由が書いてました。 Why was arguments.callee removed from ES5 strict mode? ※ 該当部分は日本語未翻訳ですので、英語版をお読みください。 一見、無名関数での再帰関数に使うのに有用と思えますが、MDNで紹介されている下記のコードの場合に、問題が発生します。 > > > ``` > var global = this; > > var sillyFunction = function (recursed) { > if (!recursed) { return arguments.callee(true); } > if (this !== global) { > alert("This is: " + this); > } else { > alert("This is the global"); > } > } > > sillyFunction(); > ``` > > このコードは次のような動作になります。 1. `sillyFunction();`と引数無しで呼びます。 呼び出されたsillyFunction関数は次の環境で実行されます。 引数はありませんので、`recursed`はundefinde値です。 レシーバが無い(関数だけの)呼び出しですので、`this`は変数`global`と同じになります。 2. `!recursed`が真偽評価が真になりますので、`return arguments.callee(true);`と引数付きで呼び出します。 呼び出されたsillyFunction関数は次の環境で実行されます。 引数は`true`ですので、`recursed`は`true`です。 レシーバが`arguments`ですので、`this`は**最初に呼び出された時の`arguments`**です。 3. `!recursed`が真偽評価が偽になりますので、飛ばされます。 4. `this`はもはや変数`global`とは異なりますので、if文の真の方が実行されます。 5. `this`がアラートで表示されます。**最初に呼び出された時の`arguments`**になっている事がわかります。 JavaScriptでは、`a.f(...)`という関数呼び出しをした場合、bind等で`this`があらかじめ束縛されていなければ、呼び出された関数`f`の中での`this`は`a`になります。**`arguments.callee(true)`という呼び出しでも同様に`this`が`arguments`になるというだけです。**ですが、この最初の呼び出しと再帰での呼び出しで`this`が変わるという動作は思わぬバグを引き起こすため、名前付きの関数式を使うことが推奨されるようになったというのが理由のようです。 また、ES6からは末尾呼び出し最適化が仕様として追加されました(ただし、ほとんどのブラウザがまだ未実装です)が、`arguments.callee`ではこの末尾呼び出し最適化によって再帰関数でもスタックを消費しないという動作の恩恵を受けることもないようです。(末尾呼び出し最適化は実装がほとんど無いため、ちょっと未確認ですが)