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

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

ただいまの
回答率

90.51%

  • JavaScript

    16490questions

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

JavaScriptのarguments.calleeは悪ですか?

解決済

回答 2

投稿

  • 評価
  • クリップ 5
  • VIEW 2,757

grovion

score 135

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

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

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+8

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ですので、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/17 06:17 編集

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

    私自身、argumentsを使ったことがこれまで一度も無かったので、arguments周りについて禁止することをすっぱりさっぱり忘れてました…。

    キャンセル

  • 2017/01/17 10:46 編集

    ご回答感謝します。

    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
    いいことと悪いことがまとまってて読みやすいです

    キャンセル

  • 2017/01/17 18:19

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

    書き直したコードはそれであっていると思います。その他、callメソッドなどでthisを指定するなどもありますが、いずれも冗長な書き方になってしまいます。

    キャンセル

+1

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

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

Re: redstone さん

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/17 01:00 編集

    ご回答感謝します。
    >>「関数は名前を付けるべき」
    これには私も同感です。

    >>関数名を付ければ再帰呼び出し可能なので arguments.callee の出番はないのだと思います。
    ただし、与えた名前を使って再帰呼出し可能だから、出番なしというのは、申し訳ないですが納得がいきません。

    なぜなら、calleeという特別な名前のお陰で、再帰呼び出しをしていることを強く明示していると思うからです。再帰呼び出しの明示という目的を果たせる思うので、出番がないとは思えないのです。

    ----------
    ここから少し分かりづらい例ですが、
    再帰呼び出しの明示が必要と思うきっかけの経験です。

    今まで以下のようなミスが何度かありました。
    以前作った実装と似たようなところがあり、その関数をコピーして、少し変えて新しい関数を作ります。
    ですが、新しく作った関数が思ったように動作しません。
    実はコピーのもとになった関数は自分自身を再帰呼び出しをしていました。
    その結果、新しく作った関数は複製元を呼び出していて、それが原因で思ったように動いていなかったです。

    自分で書いたコードでも、急いでいたり、すこし疲れていたりすると、再帰呼び出ししていることに気づかず、複製した関数を作ってしまうことがありました。

    コピー元の関数を複製して、作った関数がうまく動かなかったのは、再帰呼出しに気づかなかったのが原因です。calleeのような存在があれば、目立つので(シンタックスハイライトで少し色が変わることを期待しています)、そういうエラーを排除できることが期待できると思っています。
    また、複製した場合でもcalleeは複製元ではなく、自分自身の再帰の意味になりますので、場合によっては、再帰部分を書き直さずに済むメリットもあると考えています。

    キャンセル

  • 2017/01/17 02:07

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

    キャンセル

  • 2017/01/17 10:26

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

    キャンセル

  • 2017/01/17 13:36 編集

    > 自分で書いたコードでも、急いでいたり、すこし疲れていたりすると、再帰呼び出ししていることに気づかず、複製した関数を作ってしまうことがありました。
    疲れが原因なら arguments.callee も読み飛ばす可能性があると思います(疲れがたまる前に休憩を取って下さい)。
    疲れに原因を持っていくよりももっと深いところで原因を見つめなおす事が必要だと思いました。
    再帰している事に気が付かない理由として次が考えられます。

    - 疲れていた為、処理の流れを追えていなかった(この場合は何が書いてあってもミスをします。対策は適度に休憩する事。)
    - 変数名/関数名が的確でない為に処理の流れを追えていなかった
    - その他、整理されたソースコードを書けていなかった

    逆にいえば、arguments.callee にして解決できるのなら、適切な関数名を付ければ解決できる問題だと思います。
    また、再帰呼び出しには一つ上のスコープを再帰呼び出しする場合があります。

    function foo () {
     function bar () {
      if (x) {
       foo();
      }
     }
     bar();
    }

    この場合は arguments.callee が使えませんので関数名から再帰呼び出ししている事を意識する以外に対策はありません。
    場合によっては「再帰を使わない」も選択肢になりうると思います。

    キャンセル

  • 2017/01/18 01:04

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

    キャンセル

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

  • ただいまの回答率 90.51%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    16490questions

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