32

状況:

Javascriptの厳密モードに関して奇妙なことがわかりました。

  • 外部のサードパーティ Javascript ライブラリを使用しています。
    • 縮小され、
    • 4000行以上のコードがあり、
    • まったく使用しておらずuse 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    |
+-----------------------+-----+--------------------------------------------------------------+

注: for OS, Win= Windows 7, Mac= Mac OS 10.7.5


私の理解:

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

質問:

では、Chrome 以外のブラウザはすべて間違っているのでしょうか? それとも逆ですか?それとも、これは未定義の動作なので、ブラウザはどちらかの方法で実装することを選択できますか?

4

2 に答える 2

43

序文

これの要点に入る前に、いくつかの簡単なポイントがあります。

  • 最新のデスクトップ ブラウザはすべてuse strict...

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

  • 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特にマルチスレッド環境 (およびマルチスレッド JavaScript エンジンがあります) では、実装の問題 (さらに 1 つ以上) であり、 Bergiが質問のコメントで指摘したように、再帰コード。

callerそのため、第 5 版では、厳密な関数でプロパティを参照するとエラーがスローされるように指定することで、明示的に削除しました。(これは§13.2関数オブジェクトの作成のステップ 19 にあります。)

それは厳密な機能です。ただし、厳密ではない関数では、動作は未規定であり、実装に依存します。これが、これを正しく行うための非常に多くの異なる方法がある理由です。

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

デバッグセッションよりもインストルメント化されたコードを参照する方が簡単なので、これを使用しましょう:

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 から a5 を取得する
2. 何を得ましたか? 【オブジェクト機能】
3. a5.caller を取得する
4. 今の a5 とは? [オブジェクトヌル]

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

は厳密でない関数に対しては指定されていないため、V8 はonFunction#callerへのアクセスに対して必要なことを何でも実行できます。返品は完全に合理的です(ただし、ではなく見て驚いた)。(これについては、以下の結論で説明します...)callerfoo.barnullnullundefinednull

Firefox が正しく機能する方法

SpiderMonkey (Firefox の JavaScript エンジン) は次のことを行います。

1.arguments.callee から a5 を取得する
2. 何を得ましたか? 【オブジェクト機能】
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と、厳密モードではアクセスできないというエラーが表示されます。

私はこれを「間違っている」と呼んでいます (ただし、非常に小文字の「w」を使用します)。これは、厳密モードで行っていることはできないが、厳密モードではないことを示しているためです

しかし、これは Chrome や Firefox が行うことと同じくらい間違っていると主張することもできます。なぜなら、(再び)callerアクセスは未規定の動作だからです。そのため、IE10 の人々は、この不特定の動作を実装すると、strict モード エラーがスローされると判断しました。誤解を招くと思いますが、繰り返しになりますが、「間違っている」とすれば、それほど間違っているわけではありません。

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

1.arguments.callee から a5 を取得する
2. 何を得ましたか? 【オブジェクト機能】
3. a5.caller を取得する
4. 今の a5 とは? 【オブジェクト機能】
3. a5.caller を取得する
4. 今の a5 とは? [オブジェクトヌル]

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

結論と考察

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

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

このコードも楽しく遊べます:ライブ コピー| ライブソース

<!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>
于 2013-06-01T09:18:20.260 に答える