เหตุใดตัวดีบัก 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 dev ให้พฤติกรรมที่คาดหวังในทั้งสองสถานการณ์

เกิดอะไรขึ้นกับ Chrome ที่โปรแกรมดีบั๊กทำงานได้สะดวกกว่า Firefox ฉันได้สังเกตพฤติกรรมนี้มาระยะหนึ่งแล้วและรวมถึงรุ่น 41.0.2272.43 เบต้า (64 บิต)

มันเป็นเครื่องมือจาวาสคริปต์ของ Chrome "แบน" ฟังก์ชั่นเมื่อมันสามารถ?

ที่น่าสนใจถ้าฉันจะเพิ่มตัวแปรที่สองที่ถูกอ้างถึงในฟังก์ชั่นภายในที่xตัวแปรคือยังไม่ได้กำหนด

ฉันเข้าใจว่ามักจะมีนิสัยใจคอที่มีขอบเขตและคำจำกัดความแปรปรวนเมื่อใช้ดีบักเกอร์แบบโต้ตอบ แต่สำหรับฉันแล้วดูเหมือนว่าตามข้อกำหนดภาษานั้นควรจะเป็นทางออกที่ดีที่สุดสำหรับนิสัยใจคอเหล่านี้ ดังนั้นฉันอยากรู้ว่านี่เป็นเพราะ Chrome มีการปรับให้เหมาะสมยิ่งกว่า Firefox หรือไม่ และการเพิ่มประสิทธิภาพเหล่านี้สามารถปิดการใช้งานได้อย่างง่ายดายในระหว่างการพัฒนาหรือไม่ (อาจต้องปิดการใช้งานเมื่อเปิดเครื่องมือ dev หรือไม่)

นอกจากนี้ฉันสามารถทำซ้ำสิ่งนี้ด้วยจุดพักเช่นเดียวกับdebuggerคำสั่ง


2
บางทีมันอาจจะได้รับตัวแปรยกเลิกการใช้ออกไปจากทางของคุณสำหรับคุณ ...
dandavis

markle976 ดูเหมือนว่าจะบอกว่าสายไม่ได้เรียกว่าจริงจากภายในdebugger; barดูที่การติดตามสแต็กเมื่อหยุดในดีบักเกอร์: barฟังก์ชั่นที่กล่าวถึงในสแต็คเทรซหรือไม่? ถ้าฉันพูดถูกสแต็คควรบอกว่ามันหยุดอยู่ที่บรรทัดที่ 5 ที่บรรทัดที่ 7 ที่บรรทัดที่ 9
David Knipe

ฉันไม่คิดว่ามันจะเกี่ยวอะไรกับฟังก์ชั่นการแบน V8 ฉันคิดว่านี่เป็นเพียงการเล่นโวหาร ฉันไม่รู้ว่าฉันจะเรียกมันว่าบั๊กหรือเปล่า ฉันคิดว่าคำตอบของเดวิดด้านล่างเหมาะสมที่สุด
markle976


2
ฉันมีปัญหาเดียวกันฉันเกลียดมัน แต่เมื่อฉันต้องมีรายการการปิดการเข้าถึงในคอนโซลฉันไปที่ที่คุณสามารถดูขอบเขตค้นหารายการการปิดและเปิด แล้วคลิกขวาในองค์ประกอบที่คุณต้องการและคลิกที่ร้านเป็นตัวแปรทั่วโลก ตัวแปรโกลบอลใหม่temp1ถูกแนบกับคอนโซลและคุณสามารถใช้เพื่อเข้าถึงรายการขอบเขต
Pablo

คำตอบ:


149

ฉันพบรายงานปัญหา v8 ซึ่งมีความแม่นยำเกี่ยวกับสิ่งที่คุณถาม

ตอนนี้เพื่อสรุปสิ่งที่กล่าวในรายงานปัญหานั้น v8 สามารถจัดเก็บตัวแปรที่อยู่ในเครื่องไปยังฟังก์ชันบนสแต็กหรือในวัตถุ "บริบท" ซึ่งอาศัยอยู่บนฮีป มันจะจัดสรรตัวแปรท้องถิ่นในสแต็คตราบเท่าที่ฟังก์ชั่นไม่ได้มีฟังก์ชั่นภายในที่อ้างอิงถึงพวกเขา มันเป็นการเพิ่มประสิทธิภาพ หากฟังก์ชั่นภายในใด ๆอ้างถึงตัวแปรท้องถิ่นตัวแปรนี้จะถูกวางในวัตถุบริบท (เช่นบนฮีปแทนที่จะเป็นบนสแต็ก) กรณีของevalเป็นพิเศษ: ถ้ามันถูกเรียกโดยฟังก์ชั่นภายในตัวแปรท้องถิ่นทั้งหมดจะถูกวางในวัตถุบริบท

เหตุผลสำหรับวัตถุบริบทคือโดยทั่วไปแล้วคุณสามารถส่งคืนฟังก์ชันภายในจากภายนอกได้จากนั้นจึงสร้างสแต็กที่มีอยู่ในขณะที่ฟังก์ชันภายนอกไม่สามารถใช้งานได้อีกต่อไป ดังนั้นสิ่งใดก็ตามที่ฟังก์ชั่นภายในเข้าถึงได้เพื่อเอาตัวรอดจากฟังก์ชั่นด้านนอกและอยู่บนฮีปมากกว่าที่จะอยู่บนสแต็ก

ดีบักเกอร์ไม่สามารถตรวจสอบตัวแปรเหล่านั้นที่อยู่ในสแต็ก เกี่ยวกับปัญหาที่พบในการดีบักสมาชิกโครงการหนึ่งคนพูดว่า :

ทางออกเดียวที่ฉันคิดได้คือเมื่อใดก็ตามที่ devtools เปิดอยู่เราจะ deopt โค้ดทั้งหมดและคอมไพล์ใหม่ด้วยการจัดสรรบริบทที่บังคับ ซึ่งจะลดประสิทธิภาพลงอย่างมากเมื่อเปิดใช้งาน devtools

นี่คือตัวอย่างของ "ถ้าฟังก์ชันภายในอ้างถึงตัวแปรวางไว้ในวัตถุบริบท" ถ้าคุณเรียกคุณนี้จะสามารถเข้าถึงxที่debuggerคำสั่งถึงแม้xจะใช้เฉพาะในfooฟังก์ชั่นที่ไม่เคยเรียกว่า !

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

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

  function bar() {
    debugger;
  };

  bar();
}
baz();

13
คุณคิดหาวิธีในการ deopt code หรือไม่? ฉันชอบใช้ดีบักเกอร์เป็น REPL และรหัสที่นั่นแล้วถ่ายโอนรหัสไปยังไฟล์ของฉันเอง แต่บ่อยครั้งไม่สามารถทำได้เนื่องจากตัวแปรที่ควรมีไม่สามารถเข้าถึงได้ eval ง่าย ๆ จะไม่ทำ ฉันได้ยินเสียงอนันต์ของลูปอาจ
Ray Foss

ฉันไม่ได้พบกับปัญหานี้ในขณะทำการดีบักดังนั้นฉันจึงไม่ได้ค้นหาวิธีในการ deopt code
Louis

6
ความคิดเห็นล่าสุดจากปัญหากล่าวว่า: การนำ V8 เข้าสู่โหมดที่ทุกอย่างสามารถบังคับบริบทได้ แต่ฉันไม่แน่ใจว่าจะเปิดใช้งานเมื่อใดผ่าน Devtools UIเพื่อการดีบักบางครั้งฉันก็อยากจะทำเช่นนั้น . ฉันจะบังคับโหมดดังกล่าวได้อย่างไร
Suma

2
@ user208769 เมื่อปิดเป็นรายการที่ซ้ำกันเราชอบคำถามที่มีประโยชน์มากที่สุดสำหรับผู้อ่านในอนาคต มีปัจจัยหลายอย่างที่ช่วยพิจารณาว่าคำถามใดมีประโยชน์มากที่สุด: คำถามของคุณมี 0 คำตอบที่ถูกต้องในขณะที่คำถามนี้มีคำตอบ upvoted หลายคำ ดังนั้นคำถามนี้มีประโยชน์มากที่สุดของทั้งสอง วันที่กลายเป็นปัจจัยกำหนดเฉพาะในกรณีที่ประโยชน์มีค่าเท่ากัน
หลุยส์

1
คำตอบนี้ตอบคำถามจริง (เพราะเหตุใด) แต่คำถามโดยนัย - ฉันจะเข้าถึงตัวแปรบริบทที่ไม่ได้ใช้สำหรับการดีบักได้อย่างไรโดยไม่ต้องเพิ่มการอ้างอิงพิเศษเหล่านี้ในรหัสของฉัน - ตอบได้ดีกว่าโดย @OwnageIsMagic ด้านล่าง
Sigfried

30

Like @Louis กล่าวว่าเกิดจากการปรับแต่ง v8 คุณสามารถเข้าไปที่ Call stack เพื่อวางเฟรมที่ตัวแปรนี้มองเห็นได้:

call1 Call2

หรือแทนที่debuggerด้วย

eval('debugger');

eval จะคลี่คลายก้อนปัจจุบัน


1
เกือบดีมาก! มันหยุดชั่วคราวในโมดูล VM (สีเหลือง) ที่มีเนื้อหาdebuggerและบริบทพร้อมใช้งานแน่นอน หากคุณเพิ่มระดับสแต็กหนึ่งระดับให้กับรหัสที่คุณกำลังพยายามตรวจแก้จุดบกพร่องคุณจะกลับไปที่ไม่สามารถเข้าถึงบริบทได้ ดังนั้นจึงเป็นเพียงเล็กน้อยที่ไม่สามารถดูรหัสที่คุณกำลังดีบั๊กขณะเข้าถึงตัวแปรการปิดที่ซ่อนอยู่ ฉันจะอัปโหลดเพราะมันช่วยให้ฉันไม่ต้องเพิ่มรหัสซึ่งไม่ชัดเจนสำหรับการดีบักและทำให้ฉันสามารถเข้าถึงบริบททั้งหมดโดยไม่ทำให้แอปหมดลง
Sigfried

โอ้ ... มันยิ่งใหญ่กว่าการใช้evalหน้าต่างต้นฉบับ ed สีเหลืองเพื่อเข้าถึงบริบท: คุณไม่สามารถก้าวผ่านโค้ดได้ (เว้นแต่คุณใส่eval('debugger')ระหว่างบรรทัดทั้งหมดที่คุณต้องการผ่าน)
Sigfried

ดูเหมือนว่ามีสถานการณ์ที่ตัวแปรบางอย่างไม่สามารถมองเห็นได้แม้ว่าจะผ่านไปยังสแต็กเฟรมที่เหมาะสมแล้วก็ตาม ฉันมีสิ่งที่ต้องการcontrollers.forEach(c => c.update())และกดเบรกพอยต์แห่งหนึ่งลึก ๆ c.update()ข้างใน ถ้าฉันเลือกเฟรมที่controllers.forEach()ถูกเรียกว่าcontrollersจะไม่ได้กำหนด (แต่ทุกอย่างในเฟรมนั้นจะมองเห็นได้) ฉันทำซ้ำไม่ได้ในเวอร์ชั่นที่น้อยที่สุดฉันคิดว่าอาจมีบางระดับของความซับซ้อนที่ต้องผ่านหรือบางสิ่งบางอย่าง
PeterT

@PeterT ถ้าเป็น <undefined> คุณอยู่ผิดที่หรือsomewhere deep inside c.update()รหัสของคุณไปเป็น async และคุณเห็น async stack frame
OwnageIsMagic

6

ฉันยังสังเกตเห็นสิ่งนี้ใน nodejs ผมเชื่อว่า (และผมยอมรับว่านี้เป็นเพียงการคาดเดา) ว่าเมื่อรหัสจะรวบรวมถ้าxไม่ปรากฏภายในbarก็ไม่ได้ทำให้สามารถใช้ได้ภายในขอบเขตของx barนี่อาจทำให้มีประสิทธิภาพมากกว่าเล็กน้อย ปัญหาคือบางคนลืม (หรือไม่ได้ดูแล) ว่าแม้ไม่มีxในbarคุณอาจตัดสินใจที่จะเรียกใช้ดีบักและด้วยเหตุนี้ยังคงต้องเข้าถึงจากภายในxbar


3
ขอบคุณ โดยทั่วไปฉันต้องการที่จะสามารถอธิบายสิ่งนี้กับผู้เริ่มต้นจาวาสคริปต์ได้ดีกว่า "การดีบักอยู่"
Gabe Kopley

@GabeKopley: ในทางเทคนิคแล้วดีบักไม่ได้โกหก หากไม่มีการอ้างอิงตัวแปรแสดงว่าไม่มีการปิดล้อมทางเทคนิค ดังนั้นจึงไม่จำเป็นต้องล่ามในการสร้างการปิด
slebetman

7
นั่นไม่ใช่ประเด็น. เมื่อใช้ดีบักเกอร์ฉันมักจะตกอยู่ในสถานการณ์ที่ฉันต้องการทราบค่าของตัวแปรในขอบเขตด้านนอก แต่ทำไม่ได้เพราะสิ่งนี้ และในบันทึกทางปรัชญาที่มากกว่านี้ฉันจะบอกว่าผู้ดีบั๊กกำลังโกหก การที่ตัวแปรนั้นมีอยู่ในขอบเขตด้านในไม่ควรขึ้นอยู่กับว่ามันถูกใช้จริงหรือว่ามีevalคำสั่งที่ไม่เกี่ยวข้องหรือไม่ หากมีการประกาศตัวแปรมันควรจะสามารถเข้าถึงได้
David Knipe

2

ว้าวน่าสนใจจริงๆ!

เป็นคนอื่นได้กล่าวถึงนี้น่าจะเกี่ยวข้องกับการ แต่มากขึ้นโดยเฉพาะที่เกี่ยวข้องกับการscope debugger scopeเมื่อสคริปต์ที่ถูกฉีดถูกประเมินในเครื่องมือของนักพัฒนาดูเหมือนว่าจะกำหนด a 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/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 ที่ฉันชอบมาก BTW :)


ขอบคุณ! :) ฉันสงสัยว่า FF จะแตกต่างกันอย่างไร จากมุมมองของฉันเป็นนักพัฒนาประสบการณ์ FF คือกรรมดีกว่า ...
เกบ Kopley

2
"เรียกจุดพักในเวลาที่เหลือ" ฉันสงสัย นั่นไม่ใช่จุดพัก และฉันไม่เห็นว่าทำไมการไม่มีสิ่งอื่นในหน้าที่จึงสำคัญ ต้องบอกว่าถ้ามันเป็นอะไรอย่าง nodejs จุดพักอาจจะบั๊กมาก
David Knipe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.