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


167

このコードで:

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

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

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

ここに画像の説明を入力してください

コードを変更すると:

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

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

私は期待される結果を得ます:

ここに画像の説明を入力してください

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

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

デバッガーがFirefoxよりも便利に動作しないのは、Chromeでどうなっていますか。私はこの動作をしばらくの間、バージョン41.0.2272.43ベータ(64ビット)まで観察しました。

ChromeのJavaScriptエンジンは、可能な場合に関数を「フラット化」するのでしょうか。

私は第二の可変追加すると面白いされ、内側の関数で参照し、x変数はまだ定義されていません。

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

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


2
多分それはあなたのためにあなたの方法から未使用の変数を取得しています...
dandavis

markle976は、debugger;行が実際には内部から呼び出されていないと言っているようbarです。デバッガーで一時停止するときにスタックトレースを見てください。bar関数はスタックトレースで言及されていますか?私が正しい場合は、スタックトレースは、それがライン9で、7行目で、5行目で一時停止しています言うべき
デヴィッドKnipe

V8の平坦化関数とは関係ないと思います。これはただの癖だと思います。バグと呼べるかどうかわかりません。以下のDavidの答えが最も理にかなっていると思います。
markle976 2015


2
同じ問題があります。嫌いです。しかし、コンソールでクロージャエントリにアクセスする必要がある場合は、スコープを確認できる場所に移動し、クロージャエントリを見つけて開きます。次に、必要な要素を右クリックし、[ グローバル変数として保存 ]をクリックします。新しいグローバル変数temp1がコンソールにアタッチされ、これを使用してスコープエントリにアクセスできます。
Pablo

回答:


149

私は正確にあなたが求めていることについてのv8の問題レポートを見つけました。

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

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

デバッガーは、スタックにある変数を検査できません。デバッグで発生した問題に関して、あるプロジェクトメンバー次のように述べています

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

「内部関数が変数を参照する場合は、コンテキストオブジェクトに配置する」の例を次に示します。これを実行すると、関数でのみ使用されますが、呼び出されることはありませんxdebuggerステートメントでアクセスできます。xfoo

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

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

  function bar() {
    debugger;
  };

  bar();
}
baz();

13
コードを無効にする方法を見つけましたか?デバッガーをREPLとして使用し、そこでコードを作成してから、コードを自分のファイルに転送します。しかし、そこにあるはずの変数にアクセスできないため、実行できないことがよくあります。単純なevalではそれはできません。無限のforループが聞こえる。
Ray Foss

デバッグ中に実際にこの問題に遭遇したことはないので、コードを無効にする方法を探していません。
Louis

6
問題からの最後のコメントは言う:すべてが強制的にコンテキストが割り当てられるモードにV8を置くことは可能ですが、どのように/いつDevtools UIを通じてそれをトリガーするのかわかりませんデバッグのために、私は時々そうしたいと思います。どうすればこのようなモードを強制できますか?
スマ

2
@ user208769重複して閉じる場合、将来の読者に最も役立つ質問を優先します。どの質問が最も有用であるかを判断するのに役立つ複数の要因があります。あなたの質問は正確に0の回答を得ましたが、この質問は複数の投票された回答を得ました。したがって、この質問は2つの中で最も有用です。日付は、有用性がほぼ等しい場合にのみ決定要因になります。
ルイ

1
この回答は実際の質問(なぜですか?)に答えますが、暗黙の質問-コードに余分な参照を追加せずに、デバッグのために未使用のコンテキスト変数にアクセスするにはどうすればよいですか?-以下の@OwnageIsMagicでより適切に回答されます。
Sigfried 2017

30

@Louisのように、それはv8の最適化によって引き起こされたと言いました。この変数が表示されているフレームにコールスタックをトラバースできます。

call1 call2

OR REPLACE debugger

eval('debugger');

eval 現在のチャンクを採用しない


1
ほとんど素晴らしい!コンテンツ付きのVMモジュール(黄色)で一時停止し、debugger実際にコンテキストが利用可能です。スタックを実際にデバッグしようとしているコードに1レベル上げると、コンテキストにアクセスできない状態に戻ります。したがって、これはわずかに不格好であり、非表示のクロージャー変数にアクセスしているときにデバッグしているコードを見ることができません。ただし、明らかにデバッグ用ではないコードを追加する必要がなく、アプリ全体を最適化せずにコンテキスト全体にアクセスできるため、賛成票を投じます。
Sigfried 2017

ああ...それは黄色を使用するよりも、clunkierだevalコンテキストへのアクセスを得るために、EDソースウィンドウを:あなたはコードをステップ(あなたが入れない限りできませんeval('debugger')。全ての間の行をあなたを介してのステップにしたい)
Sigfried

適切なスタックフレームに移動した後でも、特定の変数が表示されない状況があるようです。私のようなものがcontrollers.forEach(c => c.update())あり、どこか深いところにブレークポイントがありましたc.update()。次に、controllers.forEach()呼び出されたフレームを選択すると、controllersは定義されません(ただし、そのフレーム内の他のすべてが表示されます)。最小限のバージョンでは再現できませんでした。渡す必要がある複雑さのしきい値があるのではないでしょうか。
PeterT

@PeterT <undefined>の場合、間違った場所にあるかsomewhere deep inside c.update()、コードが非同期になり、非同期スタックフレームが表示されます
OwnageIsMagic

6

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


2
ありがとう。基本的に、「デバッガーの嘘」よりも、JavaScriptの初心者にこれを説明できるようにしたいと思います。
Gabe Kopley、2015

@GabeKopley:技術的にはデバッガーは嘘をついていません。変数が参照されていない場合、技術的に囲まれていません。したがって、インタープリターがクロージャーを作成する必要はありません。
slebetman

7
それはポイントではありません。デバッガを使用しているとき、私は頻繁に外部スコープの変数の値を知りたいと思っていましたが、これが原因でできませんでした。そして、より哲学的な注意として、デバッガは嘘をついていると思います。変数が内部スコープに存在するかどうかは、その変数が実際に使用されているかどうかや、無関係なevalコマンドがあるかどうかに依存すべきではありません。変数が宣言されている場合は、アクセス可能である必要があります。
David Knipe 2017

2

うわー、本当に面白い!

他の人が述べたように、これはに関連しているようです 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();

野心的および/または好奇心が強い場合は、ソースを調べて、何が起こっているのかを確認してください:

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


0

これは変数と関数の巻き上げに関係していると思います。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質問です、ところで:)


ありがとう!:)私はFFが別様に何をするのか疑問に思っています。開発者としての私の観点からは、FFの経験は客観的に優れています...
ゲイブコプリー

2
「lex時間にブレークポイントを呼び出す」とは思いません。それはブレークポイントの目的ではありません。また、関数内に他のものがないことが重要である理由がわかりません。そうは言っても、それがnodejsのようなものであれば、ブレークポイントは非常にバグが多いかもしれません。
David
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.