web-dev-qa-db-ja.com

なぜChromeデバッガーは閉じたローカル変数が未定義であると考えるのですか?

このコードでは:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
  };
  bar();
}
baz();

私はこの予期しない結果を得ます:

enter image description here

コードを変更すると:

function baz() {
  var x = "foo";

  function bar() {
    x;
    debugger;
  };
  bar();
}

期待どおりの結果が得られます。

enter image description here

また、内部関数内でevalの呼び出しがある場合、必要に応じて変数にアクセスできます(evalに何を渡しても構いません)。

一方、Firefox開発ツールは、両方の状況で期待される動作を提供します。

デバッガーの動作がFirefoxよりも劣るというChromeの最新情報バージョン41.0.2272.43ベータ(64ビット)まで、この動作をしばらくの間観察しました。

Chromeのjavascriptエンジンは、可能な場合に機能を「平坦化」するのですか?

興味深いことに、内部関数でisが参照される2番目の変数を追加しても、x変数は未定義のままです。

対話型デバッガーを使用する場合、スコープと変数の定義にしばしば癖があることを理解していますが、言語仕様に基づいて、これらの癖に対する「最良の」解決策があるはずです。したがって、これがChromeがFirefoxよりもさらに最適化されているためかどうかは非常に興味があります。また、開発中にこれらの最適化を簡単に無効にできるかどうか(開発ツールが開いているときに無効にする必要があるかもしれません)。

また、debuggerステートメントだけでなく、ブレークポイントでもこれを再現できます。

145
Gabe Kopley

V8 issue report を見つけました。これはまさにあなたが求めているものに関するものです。

さて、その問題レポートで述べられていることをまとめると... v8は、スタック上の関数に対してローカルな変数を保存できますまたはヒープ上に存在するコンテキスト」オブジェクト。関数がそれらを参照する内部関数を含まない限り、スタックにローカル変数を割り当てます。 これは最適化ですany内部関数がローカル変数を参照する場合、この変数はコンテキストオブジェクトに置かれます(つまり、スタックではなくヒープに)。 evalの場合は特別です。内部関数によって呼び出された場合、allローカル変数がコンテキストオブジェクトに配置されます。

コンテキストオブジェクトの理由は、一般に、外側の関数から内側の関数を返し、外側の関数の実行中に存在していたスタックを使用できなくなるためです。そのため、内部関数がアクセスするものはすべて、外部関数を生き残り、スタック上ではなくヒープ上で存続する必要があります。

デバッガーは、スタック上にある変数を検査できません。デバッグで発生した問題に関して、1人のプロジェクトメンバー says

私が考えることができる唯一の解決策は、devtoolsがオンになっているときはいつでも、すべてのコードを選択解除し、強制コンテキスト割り当てで再コンパイルすることです。ただし、devtoolsを有効にするとパフォーマンスが劇的に低下します。

「内部関数が変数を参照する場合は、コンテキストオブジェクトに入れてください」の例を次に示します。これを実行すると、xdebugger関数でのみ使用されますが、xステートメントでfooにアクセスできます呼び出されない

function baz() {
  var x = "x value";
  var z = "z value";

  function foo () {
    console.log(x);
  }

  function bar() {
    debugger;
  };

  bar();
}
baz();
137
Louis

@Louisが言ったように、v8の最適化が原因です。この変数が見えるフレームに呼び出しスタックを横断できます:

call1call2

またはdebuggerを置き換えます

eval('debugger');

evalは現在のチャンクを解除します

26
OwnageIsMagic

Nodejsでもこれに気付きました。コードがコンパイルされたときに、xbar内に表示されない場合、xのスコープ内でbarを使用可能にしないと信じています(これは推測に過ぎません)。これにより、おそらくわずかに効率的になります。問題は、xbarがない場合でも、デバッガーを実行することを決定した可能性があるため、x内からbarにアクセスする必要があることを忘れた(または気にしなかった)ことです。

6
David Knipe

わあ、本当に面白い!

他の人が述べたように、これはscopeに関連しているようですが、より具体的にはdebugger scopeに関連しています。挿入されたスクリプトが開発者ツールで評価されると、ScopeChainを決定するようです。これにより、(インスペクター/デバッガーのスコープにバインドされているため)若干の奇妙な結果が生じます。あなたが投稿したもののバリエーションはこれです:

(編集-実際には、元の質問でこれに言及しています、いいね、私の悪い!

function foo() {
  var x = "bat";
  var y = "man";

  function bar() {
    console.log(x); // logs "bat"

    debugger; // Attempting to access "y" throws the following
              // Uncaught ReferenceError: y is not defined
              // However, x is available in the scopeChain. Weird!
  }
  bar();
}
foo();

野心的および/または好奇心が強い人のために、ソースをスコープ(heh)して、何が起こっているかを確認します。

https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspectorhttps://github.com/WebKit/webkit/tree/master/Source/ JavaScriptCore/debugger

2
Jack

これは変数と関数の巻き上げに関係していると思われます。 JavaScriptは、すべての変数と関数の宣言を、それらが定義されている関数の先頭に移動します。詳細はこちら: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/ =

Chromeは、関数に他に何もないため、スコープで使用できない変数でブレークポイントを呼び出しているに違いありません。これはうまくいくようです:

function baz() {
  var x = "foo";

  function bar() {
    console.log(x); 
    debugger;
  };
  bar();
}

これと同様に:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
    console.log(x);     
  };
  bar();
}

これを願って、および/または上記のリンクが役立ちます。これらは私のお気に入りのSO質問です、ところで:)

0
markle976