web-dev-qa-db-ja.com

さまざまなWebブラウザーでの「use strict」の一貫性のないスコープ(arguments.calleeおよびcallerに関する)

状況:

Javascriptでstrictモードに関して何か奇妙なものを見つけました。

  • 私は外部のサードパーティのJavaScriptライブラリを使用しています
    • 縮小されました
    • 4000行を超えるコードがあり、
    • isnotuse strictをまったく使用しない、および
    • arguments.calleeを使用しています。
  • 関数内でスコープを設定した自分のコードでuse strictを使用しています。

ライブラリが提供する関数の1つを呼び出すと、エラーがスローされます。しかしながら、

  • エラーはuse strictを使用している場合にのみスローされます
  • エラーはChrome以外のすべてのブラウザでスローされます

コード:

私は無関係なものをすべて削除し、コードを次のように減らしました( jsFiddleのオンラインデモ ):

// This comes from the minified external JS library.
// It creates a global object "foo".
(function () {
    foo = {};
    foo.bar = function (e) {
        return function () {
            var a5 = arguments.callee;
            while (a5) {
                a5 = a5.caller      // Error on this line in all browsers except Chrome
            }
        }
    }("any value here");
})();

// Here's my code.
(function() {
    "use strict";   // I enable strict mode in my own function only.

    foo.bar();
    alert("done");
})();

テスト結果:

+-----------------------+-----+--------------------------------------------------------------+
| Browser               | OS  | Error                                                        |
+-----------------------+-----+--------------------------------------------------------------+
| Chrome 27.0.1453.94 m | Win | <<NO ERROR!>>                                                |
| Opera 12.15           | Win | Unhandled Error: Illegal property access                     |
| Firefox 21.0          | Win | TypeError: access to strict mode caller function is censored |
| Safari 5.1.7          | Win | TypeError: Type error                                        |
| IE 10                 | Win | SCRIPT5043: Accessing the 'caller' property of a function or |
|                       |     |             arguments object is not allowed in strict mode   |
| Chrome 27.0.1543.93   | Mac | <<NO ERROR!>>                                                |
| Opera 12.15           | Mac | Unhandled Error: Illegal property access                     |
| Firefox 21.0          | Mac | TypeError: access to strict mode caller function is censored |
| Safari 6.0.4          | Mac | TypeError: Function.caller used to retrieve strict caller    |
+-----------------------+-----+--------------------------------------------------------------+

注:OSの場合、Win = Windows 7、Mac = Mac OS 10.7.5


私の理解:

  • すべての最新のデスクトップブラウザーはuse strictをサポートしています( 使用できます を参照)。
  • use strictのスコープは関数内にあるため、スコープ外で定義されたものはすべて影響を受けません( このスタックオーバーフローの質問 を参照)。

質問:

Chrome=を除くすべてのブラウザは間違っていますか?それとも逆ですか?または、ブラウザがどちらかの方法で実装することを選択できるように、この未定義の動作ですか?

28
Pang

序文

この詳細に入る前に、いくつかの要点を説明します。

  • すべての最新のデスクトップブラウザはuse strict...をサポートしています.

いいえ、まったくありません。 IE8はかなり最新のブラウザです (2015年にはもうない)、IE9は かなり かなり現代的なブラウザ。どちらも厳密モードをサポートしていません(IE9はその一部をサポートしています)。 IE8はWindows XPと同じくらい高いので、長い間私たちと一緒にいます。 XPは現在、サポートが終了している(まあ、MSから特別な「カスタムサポート」プランを購入できます)ですが、人々はしばらくそれを使い続けます。

  • use strictは私の関数内にスコープされているため、そのスコープ外で定義されたすべてのものは影響を受けません

結構です。仕様では、厳密でないコードでさえ、厳密モードで作成された関数を使用する方法に制限を課しています。したがって、厳密モードはそのボックスの外側に到達できます。そして実際、それはあなたが使用しているコードで起こっていることの一部です。

概観

Chrome=を除くすべてのブラウザは間違っていますか?それとも逆ですか?または、ブラウザがどちらかの方法で実装することを選択できるように、この未定義の動作ですか?

少し見てみると、次のようになります。

  1. Chromeは一方向に正しく機能しています。

  2. Firefoxは別の方法で正しく理解しています。

  3. ...そしてIE10はそれを非常にわずかに間違っています:-)(特に有害な方法ではありませんが、IE9は間違いなく間違いを犯します。)

私は他の人を見ませんでした、私たちは地面を覆っていると思いました。

問題を根本的に引き起こしているコードはこのループです

var a5 = arguments.callee;
while (a5) {
    a5 = a5.caller      // Error on this line in all browsers except Chrome
}

...これは関数オブジェクトのcallerプロパティに依存しています。それでは、始めましょう。

Function#caller

Function#callerプロパティは、第3版の仕様では定義されていません。いくつかの実装はそれを提供し、他は提供しませんでした。それは 驚くほど悪い考え (申し訳ありません、それは主観的でしたね?)実装の問題(arguments.callerよりもさらに1)、特にマルチスレッド環境(およびBergiが質問のコメントで指摘したように、マルチスレッドのJavaScriptエンジンです)。

したがって、第5版では、厳密な関数でcallerプロパティを参照するとエラーがスローされるように指定することで、明示的にそれを取り除きました。 (これは §13.2、Creating Function Objects、Step 19 にあります。)

これはstrict関数に関するものです。ただし、厳密でない関数では、動作は特定されておらず、実装に依存します。そのため、これを正しく行うにはさまざまな方法があります。

インストルメントされたコード

デバッグセッションよりもインストルメント済みコードを参照する方が簡単なので、 これを使用

console.log("1. Getting a5 from arguments.callee");
var a5 = arguments.callee;
console.log("2. What did we get? " +
            Object.prototype.toString.call(a5));
while (a5) {
    console.log("3. Getting a5.caller");
    a5 = a5.caller;      // Error on this line in all browsers except Chrome
    console.log("4. What is a5 now? " +
                Object.prototype.toString.call(a5));
}

方法Chrome正しく理解する

V8(ChromeのJavaScriptエンジン)では、上記のコードで次のようになります。

1. arguments.callee 
 2からa5を取得します。何を手に入れましたか? [オブジェクト関数] 
 3。 a5.caller 
 4を取得します。現在a5とは何ですか? [オブジェクトnull]

そのため、foo.barからarguments.callee関数への参照を取得しましたが、その非厳密関数でcallerにアクセスすると、nullが得られました。ループは終了し、エラーは発生しません。

Function#callerは厳密でない関数では指定されていないため、V [8]はfoo.barcallerにアクセスするために必要なすべてのことを実行できます。 nullを返すことは完全に合理的です(nullではなくundefinedを見て驚いたのですが)。 (以下の結論でnullに戻ります...)

Firefoxが正しく機能する方法

SpiderMonkey(FirefoxのJavaScriptエンジン)はこれを行います:

1. arguments.callee 
 2からa5を取得します。何を手に入れましたか? [オブジェクト関数] 
 3。 a5.caller 
 TypeErrorの取得:ストリクトモードの呼び出し元関数へのアクセスは検閲されます

foo.barからarguments.calleeを取得することから始めますが、その非厳密関数のcallerへのアクセスはエラーで失敗します。

繰り返しになりますが、厳密でない関数でのcallerへのアクセスは不特定の動作であるため、SpiderMonkeyの人々は望んだことを実行できます。返される関数が厳密な関数である場合、エラーをスローすることにしました。細かい線ですが、これは指定されていないため、歩くことは許可されています。

IE10がそれを非常にわずかに間違って取得する方法

JScript(IE10のJavaScriptエンジン)はこれを行います。

 1. arguments.callee 
からa5を取得します。2.何を取得しましたか? [オブジェクト関数] 
 3. a5.callerの取得
 SCRIPT5043:関数または引数オブジェクトの 'caller'プロパティへのアクセスは、厳密モードでは許可されていません

他と同様に、foo.barからarguments.callee関数を取得します。次に、非厳密関数のcallerにアクセスしようとすると、厳密モードでは実行できないというエラーが表示されます。

これを「間違った」と呼びます(ただし、very小文字の「w」を使用します)、厳密モードでは実行できないことを示しているためです。厳密モードではinではありません。

ただし、これは(=)callerアクセスが不特定の動作であるため、ChromeとFirefoxの動作が間違っているわけではないと主張できます。そのため、IE10の実装では、この未指定の動作はストリクトモードエラーをスローします。誤解を招く可能性があると思いますが、繰り返しますが、「間違っている」場合は、間違いなく間違いではありません

ところで、IE9は間違いなくこれを間違っています:

1. arguments.calleeからa5を取得する
 2。何を手に入れましたか? [オブジェクト関数] 
 3。 a5.callerを取得する
 4。現在a5とは何ですか? [オブジェクト関数] 
 3。 a5.callerを取得する
 4。現在a5とは何ですか? [オブジェクトnull]

厳密でない関数でFunction#callerを許可し、厳密な関数で許可してnullを返します。厳密な関数でcallerにアクセスしていたので、2番目のアクセスでエラーがスローされたはずであることが仕様に明記されています。

結論と観察

上記すべての興味深い点は、厳密な関数、Chrome、Firefox、およびIE10でcallerにアクセスしようとすると、エラーをスローするという明確に指定された動作に加えて、すべて(さまざまな方法で)厳密でない関数でcallerにアクセスする場合でも、厳密な関数への参照を取得するためにcallerを使用する。 Firefoxはエラーをスローしてこれを行います。 ChromeおよびIE10はnullを返すことでそれを行います。それらはすべて、nonへの参照の取得をサポートしていますcallerを介した-strict関数(非厳密関数に対して)、厳密な関数ではありません。

私はその動作がどこにも指定されていることを見つけることができません(しかし、非厳密関数のcallerは完全に指定されていません...)。それはおそらく正しいことです(tm)、指定されていないようです。

このコードは、次のコードで遊ぶのも楽しいものです。 Live Copy | ライブソース

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Strict and Loose Function#caller</title>
  <style>
    p {
      font-family: sans-serif;
      margin: 0.1em;
    }
    .err {
      color: #d00;
    }
  </style>
</head>
<body>
  <script>
    function display(msg, cls) {
        var p = document.createElement('p');
        if (cls) {
            p.className = cls;
        }
        p.innerHTML = String(msg);
        document.body.appendChild(p);
    }

    // The loose functions
    (function () {
      function loose1() {
        display("loose1 calling loose2");
        loose2();
      }
      loose1.id = "loose1"; // Since name isn't standard yet

      function loose2() {
        var c;

        try {
          display("loose2: looping through callers:");
          c = loose2;
          while (c) {
            display("loose2: getting " + c.id + ".caller");
            c = c.caller;
            display("loose2: got " +
                    ((c && c.id) || Object.prototype.toString.call(c)));
          }
          display("loose2: done");
        }
        catch (e) {
          display("loose2: exception: " +
                  (e.message || String(e)),
                  "err");
        }
      }
      loose2.id = "loose2";

      window.loose1 = loose1;

      window.loose2 = loose2;
    })();

    // The strict ones
    (function() {
      "use strict";

      function strict1() {
        display("strict1: calling strict2");
        strict2();
      }
      strict1.id = "strict1";

      function strict2() {
        display("strict2: calling loose1");
        loose1();
      }
      strict2.id = "strict2";

      function strict3() {
        display("strict3: calling strict4");
        strict4();
      }
      strict3.id = "strict3";

      function strict4() {
        var c;

        try {
          display("strict4: getting strict4.caller");
          c = strict4.caller;
        }
        catch (e) {
          display("strict4: exception: " +
                  (e.message || String(e)),
                 "err");
        }
      }
      strict4.id = "strict4";

      strict1();      
      strict3();
    })();
  </script>
</body>
</html>
41
T.J. Crowder