質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

2回答

7889閲覧

JavaScriptのarguments.calleeは悪ですか?

grovion

総合スコア145

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

1グッド

6クリップ

投稿2017/01/16 15:37

質問:arguments.calleeが駄目な理由はなんですか?

arguments.calleeは使うな!」のような記事を以前に読んだのですが(探しても見つかりませんでした)、どうしてarguments.calleeを使ってはいけないと言われているのでしょうか?

またarguments.calleeはstrictモードでも禁止されていますよね。どうしてarguments.calleeがいけないのか、いくつか問題のある例をご提示していただけると嬉しいです。

個人的には「再帰していることを明示」できる点でarguments.calleeは良いとも思っています。

raccy👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

MDNのarguments.calleeの解説の所に理由が書いてました。

Why was arguments.callee removed from ES5 strict mode?
※ 該当部分は日本語未翻訳ですので、英語版をお読みください。

一見、無名関数での再帰関数に使うのに有用と思えますが、MDNで紹介されている下記のコードの場合に、問題が発生します。

JavaScript

1var global = this; 2 3var sillyFunction = function (recursed) { 4 if (!recursed) { return arguments.callee(true); } 5 if (this !== global) { 6 alert("This is: " + this); 7 } else { 8 alert("This is the global"); 9 } 10} 11 12sillyFunction();

このコードは次のような動作になります。

  1. sillyFunction();と引数無しで呼びます。

呼び出されたsillyFunction関数は次の環境で実行されます。
引数はありませんので、recursedはundefinde値です。
レシーバが無い(関数だけの)呼び出しですので、thisは変数globalと同じになります。
2. !recursedが真偽評価が真になりますので、return arguments.callee(true);と引数付きで呼び出します。
呼び出されたsillyFunction関数は次の環境で実行されます。
引数はtrueですので、recursedtrueです。
レシーバがargumentsですので、thisは**最初に呼び出された時のarguments**です。
3. !recursedが真偽評価が偽になりますので、飛ばされます。
4. thisはもはや変数globalとは異なりますので、if文の真の方が実行されます。
5. thisがアラートで表示されます。**最初に呼び出された時のarguments**になっている事がわかります。

JavaScriptでは、a.f(...)という関数呼び出しをした場合、bind等でthisがあらかじめ束縛されていなければ、呼び出された関数fの中でのthisaになります。**arguments.callee(true)という呼び出しでも同様にthisargumentsになるというだけです。**ですが、この最初の呼び出しと再帰での呼び出しでthisが変わるという動作は思わぬバグを引き起こすため、名前付きの関数式を使うことが推奨されるようになったというのが理由のようです。

また、ES6からは末尾呼び出し最適化が仕様として追加されました(ただし、ほとんどのブラウザがまだ未実装です)が、arguments.calleeではこの末尾呼び出し最適化によって再帰関数でもスタックを消費しないという動作の恩恵を受けることもないようです。(末尾呼び出し最適化は実装がほとんど無いため、ちょっと未確認ですが)

投稿2017/01/16 20:54

編集2017/01/16 20:56
raccy

総合スコア21733

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

raccy

2017/01/16 21:18 編集

arguments.calleeを禁止している記事が見つからないとの事だったので、自分の記事に追加しておきました。 http://qiita.com/raccy/items/bf590d3c10c3f1a2846b#no_entry_sign-argumentscallee 循環参照しているような気がしないでも無いです。 私自身、argumentsを使ったことがこれまで一度も無かったので、arguments周りについて禁止することをすっぱりさっぱり忘れてました…。
grovion

2017/01/17 01:47 編集

ご回答感謝します。 sillyFunction()は読むのに時間がかかりましたが、丁寧な説明ありがとうございます。 >> arguments.callee(true)という呼び出しでも同様にthisがargumentsになるというだけです。 のところでわかりました。 私の理解を確かめるために、おつきあいお願いできますか? まとめると、 「再帰関数でthisを使っていると、calleeで再帰するとthisがargumentsに変わる」 => 「これはプログラマが通常予期しないことなので、calleeは使ってはいけない」 ってことでいいですか? `sillyFunction`を予期したとおりに書き直すなら、以下のコードの変更でOKですか? ```js var global = this; var sillyFunction = function (recursed) { const callee = arguments.callee.bind(this); // 変更点 if (!recursed) { return callee(true); } // 変更点 if (this !== global) { alert("This is: " + this); } else { alert("This is the global"); } } sillyFunction(); ``` 記事 http://qiita.com/raccy/items/bf590d3c10c3f1a2846b#no_entry_sign-argumentscallee を軽くよんで、const使ってみました:D いいことと悪いことがまとまってて読みやすいです
raccy

2017/01/17 09:19

予期しないと言うよりも、直感的な動作の予想と異なるという感じです。バグのほとんどは「こうであるはず」というプログラマーの思い込みが原因です。直感的に動作の予想と異なる場合、思い込みの違いが発生する可能性が高くなり、致命的なバグに繋がります。そのような機能はなるべく非推奨にし、より、安全な方法を提供する場合が多いです。 書き直したコードはそれであっていると思います。その他、callメソッドなどでthisを指定するなどもありますが、いずれも冗長な書き方になってしまいます。
guest

0

原則として「関数は名前を付けるべき」と考えています。
その方が分かりやすいですし、デバッグもしやすいからです。

関数名を付ければ再帰呼び出し可能なので arguments.callee の出番はないのだと思います。
それなら「arguments 自体が不要では?」と思われるかもしれませんが、arguments[i] は可変引数を作るときに有用なので…。
ES6(ES2015) で Rest parameters が出来た現在では必要性が低くなっていますが、arguments.length にはまだ価値があるのでまだ必要性はありそうです。

Re: redstone さん

投稿2017/01/16 15:46

think49

総合スコア18156

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

grovion

2017/01/16 16:02 編集

ご回答感謝します。 >>「関数は名前を付けるべき」 これには私も同感です。 >>関数名を付ければ再帰呼び出し可能なので arguments.callee の出番はないのだと思います。 ただし、与えた名前を使って再帰呼出し可能だから、出番なしというのは、申し訳ないですが納得がいきません。 なぜなら、calleeという特別な名前のお陰で、再帰呼び出しをしていることを強く明示していると思うからです。再帰呼び出しの明示という目的を果たせる思うので、出番がないとは思えないのです。 ---------- ここから少し分かりづらい例ですが、 再帰呼び出しの明示が必要と思うきっかけの経験です。 今まで以下のようなミスが何度かありました。 以前作った実装と似たようなところがあり、その関数をコピーして、少し変えて新しい関数を作ります。 ですが、新しく作った関数が思ったように動作しません。 実はコピーのもとになった関数は自分自身を再帰呼び出しをしていました。 その結果、新しく作った関数は複製元を呼び出していて、それが原因で思ったように動いていなかったです。 自分で書いたコードでも、急いでいたり、すこし疲れていたりすると、再帰呼び出ししていることに気づかず、複製した関数を作ってしまうことがありました。 コピー元の関数を複製して、作った関数がうまく動かなかったのは、再帰呼出しに気づかなかったのが原因です。calleeのような存在があれば、目立つので(シンタックスハイライトで少し色が変わることを期待しています)、そういうエラーを排除できることが期待できると思っています。 また、複製した場合でもcalleeは複製元ではなく、自分自身の再帰の意味になりますので、場合によっては、再帰部分を書き直さずに済むメリットもあると考えています。
think49

2017/01/16 17:07

> 以前作った実装と似たようなところがあり、その関数をコピーして、少し変えて新しい関数を作ります。 関数のコピーというと下記コードを想定しますが、この場合は関数を書いたソースコードをコピー&ペーストする事を想定しているのでしょうか。 function a () {} var b = a;
grovion

2017/01/17 01:26

ご返答ありがとうございますありがとうございます 曖昧な言葉で申し訳ありません。ソースコードのコピー&ペーストです。
think49

2017/01/17 16:57 編集

> 自分で書いたコードでも、急いでいたり、すこし疲れていたりすると、再帰呼び出ししていることに気づかず、複製した関数を作ってしまうことがありました。 疲れが原因なら arguments.callee も読み飛ばす可能性があると思います(疲れがたまる前に休憩を取って下さい)。 疲れに原因を持っていくよりももっと深いところで原因を見つめなおす事が必要だと思いました。 再帰している事に気が付かない理由として次が考えられます。 - 疲れていた為、処理の流れを追えていなかった(この場合は何が書いてあってもミスをします。対策は適度に休憩する事。) - 変数名/関数名が的確でない為に処理の流れを追えていなかった - その他、整理されたソースコードを書けていなかった 逆にいえば、arguments.callee にして解決できるのなら、適切な関数名を付ければ解決できる問題だと思います。 また、再帰呼び出しには一つ上のスコープを再帰呼び出しする場合があります。 function foo () {  function bar () {   if (x) {    foo();   }  }  bar(); } この場合は arguments.callee が使えませんので関数名から再帰呼び出ししている事を意識する以外に対策はありません。 場合によっては「再帰を使わない」も選択肢になりうると思います。
grovion

2017/01/17 16:04

ありがとうございます 本当にその通りです
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問