Câu hỏi Phạm vi của các biến trong JavaScript là gì?


Phạm vi của các biến trong javascript là gì? Họ có cùng một phạm vi bên trong như trái ngược với bên ngoài một chức năng? Hay nó thậm chí còn quan trọng? Ngoài ra, các biến được lưu trữ ở đâu nếu chúng được xác định trên toàn cầu?


1715
2018-02-01 08:27


gốc


Đây là một điều tốt đẹp khác liên kết để ghi nhớ vấn đề này: "Giải thích phạm vi và đóng của JavaScript". - Full-Stack Software Engineer
Đây là một bài viết giải thích nó rất độc đáo. Mọi thứ bạn cần biết về phạm vi biến Javascript - Saurab Parakh
Các đã đề cập trước đó Sách điện tử của Kyle Simpson có sẵn để đọc trên Github, và nó cho bạn biết tất cả những gì bạn cần biết về Phạm vi & Đóng cửa JavaScript. Bạn có thể tìm thấy nó ở đây: github.com/getify/You-Dont-Know-JS/blob/master/… Đó là một phần của "Bạn không biết loạt sách JS", điều này thật tuyệt vời cho mọi người muốn biết thêm về JavaScript. - 3rik82


Các câu trả lời:


Tôi nghĩ về điều tốt nhất tôi có thể làm là cung cấp cho bạn một loạt các ví dụ để nghiên cứu. Các lập trình viên Javascript được xếp hạng thực tế bằng cách hiểu rõ phạm vi của họ. Đôi khi nó có thể khá phản trực giác.

  1. Biến toàn cầu

    // global scope
    var a = 1;
    
    function one() {
      alert(a); // alerts '1'
    }
    
  2. Phạm vi cục bộ

    // global scope
    var a = 1;
    
    function two(a) {
      // local scope
      alert(a); // alerts the given argument, not the global value of '1'
    }
    
    // local scope again
    function three() {
      var a = 3;
      alert(a); // alerts '3'
    }
    
  3. Trung gian: Không có thứ như phạm vi khối trong JavaScript (ES5; ES6 giới thiệu let)

    a.

    var a = 1;
    
    function four() {
      if (true) {
        var a = 4;
      }
    
      alert(a); // alerts '4', not the global value of '1'
    }
    

    b.

    var a = 1;
    
    function one() {
      if (true) {
        let a = 4;
      }
    
      alert(a); // alerts '1' because the 'let' keyword uses block scoping
    }
    
  4. Trung gian: Thuộc tính đối tượng

    var a = 1;
    
    function Five() {
      this.a = 5;
    }
    
    alert(new Five().a); // alerts '5'
    
  5. Nâng cao: Đóng cửa

    var a = 1;
    
    var six = (function() {
      var a = 6;
    
      return function() {
        // JavaScript "closure" means I have access to 'a' in here,
        // because it is defined in the function in which I was defined.
        alert(a); // alerts '6'
      };
    })();
    
  6. Nâng cao: Độ phân giải phạm vi dựa trên nguyên mẫu

    var a = 1;
    
    function seven() {
      this.a = 7;
    }
    
    // [object].prototype.property loses to
    // [object].property in the lookup chain. For example...
    
    // Won't get reached, because 'a' is set in the constructor above.
    seven.prototype.a = -1;
    
    // Will get reached, even though 'b' is NOT set in the constructor.
    seven.prototype.b = 8;
    
    alert(new seven().a); // alerts '7'
    alert(new seven().b); // alerts '8'
    

  7. Toàn cầu + Địa phương: Trường hợp phức tạp hơn

    var x = 5;
    
    (function () {
        console.log(x);
        var x = 10;
        console.log(x); 
    })();
    

    Điều này sẽ in ra undefined và 10 thay vì 5 và 10 vì JavaScript luôn di chuyển các khai báo biến (không phải là khởi tạo) lên đầu phạm vi, làm cho mã tương đương với:

    var x = 5;
    
    (function () {
        var x;
        console.log(x);
        x = 10;
        console.log(x); 
    })();
    
  8. Biến phạm vi điều khoản bắt

    var e = 5;
    console.log(e);
    try {
        throw 6;
    } catch (e) {
        console.log(e);
    }
    console.log(e);
    

    Điều này sẽ in ra 5, 6, 5. Bên trong điều khoản bắt e đổ bóng các biến cục bộ và cục bộ. Nhưng phạm vi đặc biệt này chỉ dành cho biến bị bắt. Nếu bạn viết var f; bên trong điều khoản bắt, sau đó nó chính xác giống như nếu bạn đã xác định nó trước hoặc sau khối try-catch.


2267
2018-02-01 08:58



Thậm chí không gần được toàn diện, nhưng điều này có lẽ là phải biết tập hợp các thủ thuật phạm vi Javascript một nhu cầu để có hiệu quả thậm chí ĐỌC javascript hiện đại. - Triptych
Một câu trả lời được đánh giá cao, không chắc chắn tại sao. Nó chỉ là một loạt các ví dụ mà không có lời giải thích thích hợp, sau đó dường như gây nhầm lẫn thừa kế nguyên mẫu (tức là độ phân giải tài sản) với chuỗi phạm vi (tức là độ phân giải biến). Một giải thích toàn diện (và chính xác) về phạm vi và độ phân giải thuộc tính nằm trong liên kết comp.lang.javascript Ghi chú FAQ. - RobG
@RobG Nó được đánh giá cao bởi vì nó rất hữu ích và dễ hiểu đối với một loạt các lập trình viên, mặc dù nhỏ. Các liên kết bạn đã đăng, trong khi hữu ích cho một số chuyên gia, là không thể hiểu cho hầu hết mọi người viết Javascript ngày hôm nay. Vui lòng sửa bất kỳ vấn đề về danh pháp nào bằng cách chỉnh sửa câu trả lời. - Triptych
@ triptych — Tôi chỉ chỉnh sửa câu trả lời để sửa những thứ nhỏ nhặt, chứ không phải chính. Thay đổi "phạm vi" thành "thuộc tính" sẽ khắc phục lỗi, nhưng không phải là vấn đề phối hợp thừa kế và phạm vi mà không có sự phân biệt rất rõ ràng. - RobG
Nếu bạn định nghĩa một biến trong phạm vi bên ngoài, và sau đó có một câu lệnh if xác định một biến bên trong hàm có cùng tên, ngay cả khi không đạt được nhánh nó được định nghĩa lại. Một ví dụ - jsfiddle.net/3CxVm - Chris S


Javascript sử dụng các chuỗi phạm vi để thiết lập phạm vi cho một hàm nhất định. Thường có một phạm vi toàn cầu và mỗi hàm được định nghĩa có phạm vi lồng nhau của riêng nó. Bất kỳ hàm nào được định nghĩa bên trong một hàm khác đều có một phạm vi cục bộ được liên kết với hàm bên ngoài. Nó luôn là vị trí trong nguồn xác định phạm vi.

Một phần tử trong chuỗi phạm vi về cơ bản là một Bản đồ có một con trỏ đến phạm vi cha mẹ của nó.

Khi giải quyết một biến, javascript bắt đầu ở phạm vi bên trong và tìm kiếm bên ngoài.


219
2018-02-01 08:35



Chuỗi phạm vi là một thuật ngữ khác cho [bộ nhớ] Đóng cửa... cho những người đọc ở đây để tìm hiểu / tham gia vào javascript. - New Alexandria


Các biến được khai báo trên toàn cầu có phạm vi toàn cục. Các biến được khai báo bên trong một hàm được phạm vi cho hàm đó, và đổ bóng các biến toàn cục có cùng tên.

(Tôi chắc chắn có rất nhiều sự tinh tế mà các lập trình viên JavaScript thực sự có thể chỉ ra trong các câu trả lời khác. trang này về chính xác this có nghĩa là bất cứ lúc nào. Hy vọng liên kết giới thiệu này nhiều hơn là đủ để giúp bạn bắt đầu.)


93
2018-02-01 08:31



Tôi sợ thậm chí bắt đầu trả lời câu hỏi này. Là một Lập trình viên Javascript thực, tôi biết câu trả lời có thể nhanh chóng ra sao. Bài viết hay. - Triptych
@Triptych: Tôi biết những gì bạn có ý nghĩa về những thứ nhận được ra khỏi bàn tay, nhưng xin vui lòng thêm câu trả lời. Tôi đã nhận được ở trên chỉ từ làm một vài tìm kiếm ... một câu trả lời được viết bởi một người có kinh nghiệm thực tế là bị ràng buộc trở nên tốt hơn. Xin vui lòng sửa bất kỳ câu trả lời của tôi mà chắc chắn là sai mặc dù! - Jon Skeet
Bằng cách nào đó Jon Skeet chịu trách nhiệm cho câu trả lời phổ biến nhất của tôi trên Stack Overflow. - Triptych


JavaScript trường học cũ

Theo truyền thống, JavaScript thực sự chỉ có hai loại phạm vi:

  1. Phạm vi toàn cầu : Các biến được biết trong suốt ứng dụng, từ khi bắt đầu ứng dụng (*)
  2. Phạm vi chức năng : Các biến được biết trong chức năng chúng được khai báo, từ khi bắt đầu hàm (*)

Tôi sẽ không giải thích về điều này, vì đã có nhiều câu trả lời khác giải thích sự khác biệt.


JavaScript hiện đại

Các thông số kỹ thuật JavaScript gần đây nhất bây giờ cũng cho phép phạm vi thứ ba:

  1. Phạm vi khối : Các biến được biết trong khốichúng được khai báo, từ thời điểm chúng được khai báo trở đi (**)

Làm cách nào để tạo các biến phạm vi khối?

Theo truyền thống, bạn tạo các biến của bạn như thế này:

var myVariable = "Some text";

Các biến phạm vi khối được tạo như sau:

let myVariable = "Some text";

Vậy sự khác biệt giữa phạm vi chức năng và phạm vi khối là gì?

Để hiểu sự khác biệt giữa phạm vi chức năng và phạm vi khối, hãy xem xét mã sau:

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

Ở đây, chúng ta có thể thấy rằng biến của chúng ta j chỉ được biết đến trong vòng lặp đầu tiên, nhưng không phải trước và sau. Tuy nhiên, biến của chúng ta i được biết đến trong toàn bộ hàm.

Ngoài ra, hãy xem xét rằng các biến phạm vi khối không được biết trước khi chúng được khai báo vì chúng không được treo. Bạn cũng không được phép redeclare cùng một khối scoped biến trong cùng một khối. Điều này làm cho các biến có phạm vi khối ít bị lỗi hơn các biến toàn cục hoặc hàm có phạm vi, được hoisted và không tạo ra bất kỳ lỗi nào trong trường hợp có nhiều khai báo.


Có an toàn để sử dụng các biến phạm vi khối ngày hôm nay không?

Có hay không nó là an toàn để sử dụng ngày hôm nay, phụ thuộc vào môi trường của bạn:

  • Nếu bạn đang viết mã JavaScript phía máy chủ (Node.js), bạn có thể sử dụng an toàn let tuyên bố.

  • Nếu bạn đang viết mã JavaScript phía máy khách và sử dụng trình chuyển đổi (như Traceur), bạn có thể sử dụng an toàn let tuyên bố, tuy nhiên mã của bạn có thể là bất cứ điều gì nhưng tối ưu đối với hiệu suất.

  • Nếu bạn đang viết mã JavaScript phía máy khách và không sử dụng trình chuyển đổi, bạn cần xem xét hỗ trợ trình duyệt.

    Hôm nay, ngày 23 tháng 2 năm 2016, đây là một số trình duyệt không hỗ trợ let hoặc chỉ hỗ trợ một phần:

    • Trình khám phá Internet 10 và bên dưới (không hỗ trợ)
    • Firefox 43 và bên dưới (không hỗ trợ)
    • Safari 9 và bên dưới (không hỗ trợ)
    • Opera Mini 8 và bên dưới (không hỗ trợ)
    • Trình duyệt Android 4 và bên dưới (không hỗ trợ)
    • Opera 36 và dưới (hỗ trợ một phần)
    • Chome 51 và dưới (hỗ trợ một phần)

enter image description here


Cách theo dõi hỗ trợ trình duyệt

Để biết tổng quan cập nhật về trình duyệt nào hỗ trợ let tuyên bố tại thời điểm bạn đọc câu trả lời này, xem điều này Can I Use trang.


(*) Các biến toàn cầu và phạm vi chức năng có thể được khởi tạo và sử dụng trước khi chúng được khai báo bởi vì các biến JavaScript là hoisted. Điều này có nghĩa là các khai báo luôn nằm ở đầu phạm vi.

(**) Chặn các biến phạm vi không được treo


55
2018-02-23 18:51



"KHÔNG được biết" là gây hiểu lầm, bởi vì biến được tuyên bố có do cẩu. - Oriol
Ví dụ trên là sai lạc, các biến 'i' và 'j' không được biết bên ngoài khối. Biến 'Let' chỉ có phạm vi trong khối cụ thể đó không nằm ngoài khối. Hãy có những lợi thế khác là tốt, bạn không thể redeclare biến một lần nữa và nó giữ phạm vi từ vựng. - zakir
@Oriol: Cuối cùng đã cải thiện câu trả lời của tôi và địa chỉ cẩu. Cảm ơn bạn đã chỉ ra câu trả lời cần cải thiện của tôi. Tôi cũng đã thực hiện một số cải tiến khác. - John Slegers
@JonSchneider: Đúng vậy! Nơi tôi nói "JavaScript trường cũ", tôi đang nói về ECMAScript 5 và nơi tôi đang đề cập đến "JavaScript hiện đại", tôi đang dùng về ECMAScript 6 (aka ECMAScript 2015). Tuy nhiên, tôi không nghĩ rằng điều quan trọng là phải đi vào chi tiết ở đây, vì hầu hết mọi người chỉ muốn biết (1) sự khác biệt giữa phạm vi khối và phạm vi chức năng là gì (2) trình duyệt hỗ trợ phạm vi khối và (3) cho dù đó là an toàn để sử dụng phạm vi khối ngày hôm nay cho bất kỳ dự án mà họ đang làm việc trên. Vì vậy, tôi tập trung câu trả lời của tôi vào việc giải quyết những vấn đề đó. - John Slegers
@ JonSchneider: (tiếp theo) Tuy nhiên, tôi chỉ cần thêm một liên kết đến bài viết trên tạp chí Smashing trên ES6 / ES2015 cho những ai muốn tìm hiểu thêm về các tính năng đã được thêm vào JavaScript trong vài năm qua ... của bất kỳ ai khác có thể tự hỏi ý tôi là gì với "JavaScript hiện đại". - John Slegers


Đây là một ví dụ:

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  }; 

}

</script>

Bạn sẽ muốn điều tra các bao đóng và cách sử dụng chúng để thực hiện thành viên riêng.


35
2018-02-01 08:48





Điều quan trọng, như tôi đã hiểu, là Javascript có phạm vi mức chức năng so với phạm vi khối C phổ biến hơn.

Đây là một bài viết hay về chủ đề này.


28
2018-05-15 17:38





Trong "Javascript 1.7" (phần mở rộng của Mozilla cho Javascript), người ta cũng có thể khai báo các biến khối phạm vi với let tuyên bố:

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4

23
2018-04-06 11:19



Vâng, nhưng nó có an toàn để sử dụng không? Tôi có nghĩa là tôi thực tế sẽ chọn thực hiện này nếu mã của tôi sẽ chạy trong WebKit? - Igor Ganapolsky
@Python: Không, WebKit không hỗ trợ let. - kennytm
Tôi đoán việc sử dụng hợp lệ duy nhất cho điều này sẽ là nếu bạn biết tất cả các khách hàng sẽ sử dụng một trình duyệt Mozilla như cho một hệ thống nội bộ công ty. - GazB
Hoặc nếu bạn đang lập trình bằng cách sử dụng khung công tác XUL, khung giao diện của Mozilla, nơi bạn xây dựng bằng cách sử dụng css, xml và javascript. - Gerard ONeill
@ GazB thậm chí đó là một ý tưởng kinh khủng! Vì vậy, hôm nay bạn biết rằng khách hàng của bạn đang sử dụng Mozilla sau đó ra đi kèm một bản ghi nhớ mới nói rằng bây giờ họ đang sử dụng cái gì khác. I E. lý do hệ thống trả tiền của chúng tôi hút ... Bạn phải sử dụng IE8 và không bao giờ IE9 hoặc IE10 hoặc Firefox hoặc Chrome bởi vì nó phẳng ra sẽ không làm việc ... - buzzsawddog