序文
これの要点に入る前に、いくつかの簡単なポイントがあります。
- 最新のデスクトップ ブラウザはすべて
use strict
...
いいえ、まったくありません。IE8 はかなり最新のブラウザー (2015 年にはもうありません)であり、IE9 はかなり最新のブラウザーです。どちらも厳密モードをサポートしていません (IE9 はその一部をサポートしています)。IE8 は、Windows XP と同じくらい高いので、長い間使用されるでしょう。XP は現在完全に寿命を迎えていますが (MS から特別な「カスタム サポート」プランを購入できます)、人々はしばらくの間 XP を使い続けるでしょう。
- は
use strict
関数内にスコープされているため、そのスコープ外で定義されたものはすべて影響を受けません
そうではありません。この仕様では、厳格でないコードでも厳格モードで作成された関数を使用する方法に制限を課しています。したがって、厳密モードはそのボックスの外に到達できます。実際、それはあなたが使用しているコードで起こっていることの一部です。
概要
では、Chrome 以外のブラウザはすべて間違っているのでしょうか? それとも逆ですか?それとも、これは未定義の動作なので、ブラウザはどちらかの方法で実装することを選択できますか?
少し調べてみると、次のようになります。
Chrome は一方向で正しく動作し、
Firefox は別の方法で正しく処理しています。
...そして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
アクセスすると、 が得られました。ループは終了し、エラーは発生しません。caller
null
は厳密でない関数に対しては指定されていないため、V8 はonFunction#caller
へのアクセスに対して必要なことを何でも実行できます。返品は完全に合理的です(ただし、ではなく見て驚いた)。(これについては、以下の結論で説明します...)caller
foo.bar
null
null
undefined
null
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>