Tại sao trình gỡ lỗi Chrome nghĩ rằng biến cục bộ đóng không được xác định?


167

Với mã này:

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

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

Tôi nhận được kết quả bất ngờ này:

nhập mô tả hình ảnh ở đây

Khi tôi thay đổi mã:

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

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

Tôi nhận được kết quả mong đợi:

nhập mô tả hình ảnh ở đây

Ngoài ra, nếu có bất kỳ lệnh gọi nào evalbên trong hàm bên trong, tôi có thể truy cập vào biến của mình như tôi muốn làm (không quan trọng tôi chuyển đến cái gì eval).

Trong khi đó, các công cụ dev của Firefox đưa ra hành vi dự kiến ​​trong cả hai trường hợp.

Có chuyện gì với Chrome mà trình gỡ lỗi hoạt động kém thuận tiện hơn Firefox? Tôi đã quan sát hành vi này một thời gian, lên đến và bao gồm cả Phiên bản 41.0.2272.43 beta (64-bit).

Có phải công cụ javascript của Chrome "làm phẳng" các chức năng khi có thể?

Thật thú vị nếu tôi thêm một biến thứ hai được tham chiếu trong hàm bên trong, xbiến vẫn không được xác định.

Tôi hiểu rằng thường có các quirks có phạm vi và định nghĩa biến khi sử dụng trình gỡ lỗi tương tác, nhưng đối với tôi, dựa trên đặc tả ngôn ngữ nên có một giải pháp "tốt nhất" cho các quirks này. Vì vậy, tôi rất tò mò nếu điều này là do Chrome tối ưu hóa hơn Firefox. Và cũng có thể dễ dàng vô hiệu hóa các tối ưu hóa này trong quá trình phát triển hay không (có lẽ chúng nên bị vô hiệu hóa khi các công cụ dev được mở?).

Ngoài ra, tôi có thể tái tạo điều này với các điểm dừng cũng như debuggertuyên bố.


2
có thể đó là những biến không được sử dụng theo cách của bạn ...
dandavis

markle976 dường như đang nói rằng debugger;dòng không thực sự được gọi từ bên trong bar. Vì vậy, hãy nhìn vào dấu vết ngăn xếp khi nó tạm dừng trong trình gỡ lỗi: barHàm có được đề cập trong stacktrace không? Nếu tôi đúng, thì stacktrace sẽ nói rằng nó đã tạm dừng ở dòng 5, ở dòng 7, ở dòng 9.
David Knipe 10/2/2015

Tôi không nghĩ nó có liên quan gì đến các chức năng làm phẳng V8. Tôi nghĩ rằng đây chỉ là một sự châm biếm; Tôi không biết nếu tôi thậm chí gọi nó là một lỗi. Tôi nghĩ câu trả lời của David dưới đây có ý nghĩa nhất.
markle976


2
Tôi có cùng một vấn đề, tôi ghét nó. Nhưng khi tôi cần có các mục đóng cửa truy cập trong bảng điều khiển, tôi đi đến nơi bạn có thể thấy phạm vi, tìm mục Đóng và mở nó. Sau đó nhấp chuột phải vào yếu tố bạn cần và nhấp vào Store dưới dạng Biến toàn cầu . Một biến toàn cục mới temp1được gắn vào bàn điều khiển và bạn có thể sử dụng nó để truy cập vào phạm vi.
Pablo

Câu trả lời:


149

Tôi đã tìm thấy một báo cáo vấn đề v8 chính xác về những gì bạn đang hỏi.

Bây giờ, Để tóm tắt những gì được nói trong báo cáo vấn đề đó ... v8 có thể lưu trữ các biến cục bộ cho một hàm trên ngăn xếp hoặc trong một đối tượng "bối cảnh" nằm trên heap. Nó sẽ phân bổ các biến cục bộ trên ngăn xếp miễn là hàm không chứa bất kỳ hàm bên trong nào đề cập đến chúng. Nó là một tối ưu hóa . Nếu bất kỳ hàm bên trong nào đề cập đến một biến cục bộ, biến này sẽ được đặt trong một đối tượng bối cảnh (tức là trên heap thay vì trên stack). Trường hợp evalđặc biệt: nếu nó được gọi bởi một hàm bên trong, tất cả các biến cục bộ được đặt trong đối tượng bối cảnh.

Lý do cho đối tượng bối cảnh là nói chung, bạn có thể trả về một hàm bên trong từ hàm ngoài và sau đó ngăn xếp tồn tại trong khi hàm ngoài chạy sẽ không còn khả dụng nữa. Vì vậy, bất cứ thứ gì mà hàm bên trong truy cập phải tồn tại ở hàm ngoài và sống trên heap chứ không phải trên stack.

Trình gỡ lỗi không thể kiểm tra các biến đó trên ngăn xếp. Liên quan đến vấn đề gặp phải khi gỡ lỗi, một Thành viên Dự án cho biết :

Giải pháp duy nhất tôi có thể nghĩ đến là bất cứ khi nào devtools được bật, chúng tôi sẽ mở lại tất cả mã và biên dịch lại với phân bổ ngữ cảnh bắt buộc. Điều đó sẽ hồi quy đáng kể hiệu năng với devtools được kích hoạt mặc dù.

Đây là một ví dụ về "nếu bất kỳ hàm bên trong nào tham chiếu đến biến, hãy đặt nó vào một đối tượng ngữ cảnh". Nếu bạn chạy nó, bạn sẽ có thể truy cập xvào debuggercâu lệnh mặc dù xchỉ được sử dụng trong foohàm, cái này không bao giờ được gọi !

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

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

  function bar() {
    debugger;
  };

  bar();
}
baz();

13
Bạn đã tìm ra một cách để mở mã? Tôi thích sử dụng trình gỡ lỗi làm REPL và mã ở đó sau đó chuyển mã vào các tệp của riêng tôi. Nhưng điều đó thường không khả thi vì các biến nên không thể truy cập được. Một eval đơn giản sẽ không làm điều đó. Tôi nghe thấy một vòng lặp vô hạn có thể.
Ray Foss

Tôi đã không thực sự gặp phải vấn đề này trong khi gỡ lỗi vì vậy tôi đã không tìm cách để mở mã.
Louis

6
Nhận xét cuối cùng từ vấn đề này cho biết: Đưa V8 vào chế độ trong đó mọi thứ đều có thể buộc phải phân bổ bối cảnh, nhưng tôi không chắc chắn làm thế nào / khi nào kích hoạt điều đó thông qua Devtools UI Vì lý do gỡ lỗi, đôi khi tôi muốn làm như vậy . Làm thế nào tôi có thể buộc chế độ như vậy?
Suma

2
@ user208769 Khi đóng dưới dạng trùng lặp, chúng tôi ủng hộ câu hỏi hữu ích nhất cho độc giả tương lai. Có nhiều yếu tố giúp xác định câu hỏi nào hữu ích nhất: câu hỏi của bạn có chính xác 0 câu trả lời trong khi câu hỏi này có nhiều câu trả lời nâng cao. Vì vậy, câu hỏi này là hữu ích nhất trong hai. Ngày trở thành một yếu tố quyết định chỉ khi tính hữu dụng gần như bằng nhau.
Louis

1
Câu trả lời này trả lời câu hỏi thực tế (Tại sao?), Nhưng câu hỏi ngụ ý - Làm cách nào tôi có quyền truy cập vào các biến bối cảnh không sử dụng để gỡ lỗi mà không cần thêm tham chiếu đến chúng trong mã của mình? - được trả lời tốt hơn bởi @OwnageIsMagic dưới đây.
Sigfried

30

Giống như @Louis nói nó gây ra bởi tối ưu hóa v8. Bạn có thể di chuyển ngăn xếp cuộc gọi đến khung nơi biến này hiển thị:

cuộc gọi1 gọi2

Hoặc thay thế debuggerbằng

eval('debugger');

eval sẽ mở ra chunk hiện tại


1
Gần như tuyệt vời! Nó tạm dừng trong một mô-đun VM (màu vàng) với nội dung debuggervà bối cảnh thực sự có sẵn. Nếu bạn tăng cấp một ngăn xếp lên mã bạn thực sự đang cố gắng gỡ lỗi, bạn sẽ quay lại không có quyền truy cập vào ngữ cảnh. Vì vậy, nó chỉ hơi lộn xộn, không thể nhìn vào mã bạn đang gỡ lỗi trong khi truy cập các biến đóng ẩn. Mặc dù vậy, tôi sẽ nâng cấp vì nó giúp tôi không phải thêm mã mà không rõ ràng để gỡ lỗi và nó cho phép tôi truy cập vào toàn bộ bối cảnh mà không cần mở rộng toàn bộ ứng dụng.
Sigfried

Ồ ... thậm chí còn khó khăn hơn việc phải sử dụng evalcửa sổ nguồn ed màu vàng để có quyền truy cập vào ngữ cảnh: bạn không thể bước qua mã (trừ khi bạn đặt eval('debugger')giữa tất cả các dòng bạn muốn bước qua.)
Sigfried

Dường như có những tình huống trong đó các biến nhất định là vô hình ngay cả sau khi đi qua khung ngăn xếp thích hợp; Tôi có một cái gì đó giống như controllers.forEach(c => c.update())và đạt một điểm dừng ở đâu đó sâu bên trong c.update(). Nếu sau đó tôi chọn khung controllers.forEach()được gọi controllerslà không xác định (nhưng mọi thứ khác trong khung đó đều hiển thị). Tôi không thể sao chép với một phiên bản tối thiểu, tôi cho rằng có thể có một số ngưỡng phức tạp cần phải vượt qua hoặc một cái gì đó.
PeterT

@PeterT nếu đó là <không xác định> bạn đang ở sai vị trí Hoặc somewhere deep inside c.update()mã của bạn không đồng bộ và bạn thấy khung ngăn xếp không đồng bộ
ownageIsMagic

6

Tôi cũng đã nhận thấy điều này trong nodejs. Tôi tin rằng (và tôi thừa nhận đây chỉ là dự đoán) rằng khi mã được biên dịch, nếu xkhông xuất hiện bên trong bar, nó sẽ không xkhả dụng trong phạm vi bar. Điều này có lẽ làm cho nó hiệu quả hơn một chút; vấn đề là ai đó quên (hoặc không quan tâm) mà thậm chí nếu không có xtrong bar, bạn có thể quyết định để chạy các chương trình gỡ rối và do đó vẫn cần phải truy cập xtừ bên trong bar.


2
Cảm ơn. Về cơ bản tôi muốn có thể giải thích điều này cho người mới bắt đầu sử dụng javascript tốt hơn là "Trình gỡ lỗi nói dối".
Gabe Kopley

@GabeKopley: Về mặt kỹ thuật, trình gỡ lỗi không nói dối. Nếu một biến không được tham chiếu thì nó không được bao bọc về mặt kỹ thuật. Do đó, không cần người phiên dịch để tạo ra bao đóng.
slebetman

7
Đó không phải là vấn đề. Khi sử dụng trình gỡ lỗi, tôi thường xuyên ở trong tình huống tôi muốn biết giá trị của một biến trong phạm vi bên ngoài, nhưng không thể vì điều này. Và trên một ghi chú triết học hơn, tôi muốn nói rằng trình gỡ lỗi đang nói dối. Việc biến tồn tại trong phạm vi bên trong không nên phụ thuộc vào việc nó thực sự được sử dụng hay liệu có một evallệnh không liên quan hay không . Nếu biến được khai báo, nó sẽ có thể truy cập được.
David Knipe

2

Wow, thực sự thú vị!

Như những người khác đã đề cập, điều này dường như có liên quan đến scope, nhưng cụ thể hơn, liên quan đến debugger scope. Khi tập lệnh được chèn được đánh giá trong các công cụ dành cho nhà phát triển, nó dường như xác định mộtScopeChain , điều này dẫn đến một số điều khó hiểu (vì nó bị ràng buộc với phạm vi của trình kiểm tra / trình gỡ lỗi). Một biến thể của những gì bạn đã đăng là đây:

(EDIT - thực sự, bạn đề cập đến điều này trong câu hỏi ban đầu của bạn, yike, xấu của tôi! )

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();

Đối với những người tham vọng và / hoặc tò mò, hãy tìm ra nguồn để xem điều gì đang xảy ra:

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


0

Tôi nghi ngờ điều này có liên quan đến biến và chức năng cẩu. JavaScript đưa tất cả các khai báo biến và hàm lên trên cùng của hàm mà chúng được xác định. Thông tin thêm ở đây: http://jamesallardice.com/explained-feft-and-variable-ho hiện-in-javascript /

Tôi cá rằng Chrome đang gọi điểm dừng với biến không khả dụng trong phạm vi vì không có gì khác trong hàm. Điều này dường như làm việc:

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

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

Như thế này:

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

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

Hy vọng điều này, và / hoặc các liên kết ở trên giúp. Đây là loại câu hỏi SO yêu thích của tôi, BTW :)


Cảm ơn! :) Tôi đang tự hỏi những gì FF làm khác nhau. Từ quan điểm của tôi với tư cách là một nhà phát triển, trải nghiệm FF tốt hơn một cách khách quan ...
Gabe Kopley

2
"Gọi điểm dừng tại thời điểm lex" Tôi nghi ngờ điều đó. Đó không phải là điểm dừng dành cho. Và tôi không thấy lý do tại sao sự vắng mặt của những thứ khác trong chức năng lại quan trọng. Phải nói rằng, nếu đó là bất cứ thứ gì như nodejs thì các điểm dừng có thể rất có lỗi.
David Knipe 9/2/2015
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.