Là các biến được khai báo với let hoặc const không được nâng lên trong ES6?


266

Tôi đã chơi với ES6 được một thời gian và tôi nhận thấy rằng trong khi các biến được khai báo với varđược nâng lên như mong đợi ...

console.log(typeof name); // undefined
var name = "John";

... các biến được khai báo có lethoặc constdường như có một số vấn đề với cẩu:

console.log(typeof name); // ReferenceError
let name = "John";

console.log(typeof name); // ReferenceError
const name = "John";

Điều này có nghĩa là các biến được khai báo có lethoặc constkhông được nâng lên? Điều gì đang thực sự xảy ra ở đây? Có sự khác biệt giữa letconsttrong vấn đề này?

Câu trả lời:


346

@thefourtheye là chính xác khi nói rằng các biến này không thể được truy cập trước khi chúng được khai báo. Tuy nhiên, nó phức tạp hơn thế một chút.

Là các biến được khai báo có lethoặc constkhông được nâng lên? Điều gì đang thực sự xảy ra ở đây?

Tất cả các tờ khai ( var, let, const, function, function*, class) đang "treo" trong JavaScript. Điều này có nghĩa là nếu một tên được khai báo trong một phạm vi, trong phạm vi đó, định danh sẽ luôn tham chiếu biến cụ thể đó:

x = "global";
// function scope:
(function() {
    x; // not "global"

    var/let/… x;
}());
// block scope (not for `var`s):
{
    x; // not "global"

    let/const/… x;
}

Điều này đúng cho cả phạm vi chức năng và khối 1 .

Sự khác biệt giữa var/ function/ function*khai báo và let/ const/ classkhai báo là khởi tạo .
Cái trước được khởi tạo với undefinedhoặc hàm (trình tạo) ngay khi liên kết được tạo ở đầu phạm vi. Các biến được khai báo từ vựng tuy nhiên vẫn chưa được khởi tạo . Điều này có nghĩa là một ReferenceErrorngoại lệ được đưa ra khi bạn cố gắng truy cập nó. Nó sẽ chỉ nhận được khởi tạo khi let/ const/ classtuyên bố được đánh giá, mọi thứ trước (trên) được gọi là vùng chết thời gian .

x = y = "global";
(function() {
    x; // undefined
    y; // Reference error: y is not defined

    var x = "local";
    let y = "local";
}());

Lưu ý rằng một let y;câu lệnh khởi tạo biến với undefinedlike let y = undefined;sẽ có.

Vùng chết tạm thời không phải là một vị trí cú pháp, mà là thời gian giữa việc tạo (phạm vi) biến và khởi tạo. Không phải là lỗi khi tham chiếu biến trong mã phía trên khai báo miễn là mã đó không được thực thi (ví dụ: thân hàm hoặc mã đơn giản là mã chết) và nó sẽ đưa ra một ngoại lệ nếu bạn truy cập vào biến trước khi khởi tạo ngay cả khi truy cập mã nằm dưới phần khai báo (ví dụ: trong phần khai báo hàm được gọi là quá sớm).

Có sự khác biệt giữa letconsttrong vấn đề này?

Không, họ làm việc tương tự như việc nâng hàng được xem xét. Sự khác biệt duy nhất giữa chúng là một constcon kiến ​​phải và chỉ có thể được chỉ định trong phần khởi tạo của khai báo ( const one = 1;, cả hai const one;và các lần gán lại sau như thế one = 2đều không hợp lệ).

1: varkhai báo vẫn chỉ hoạt động ở cấp độ chức năng, tất nhiên


16
Tôi thấy rằng một cái gì đó như let foo = () => bar; let bar = 'bar'; foo();minh họa tất cả các tuyên bố có hiệu lực thậm chí còn tốt hơn, bởi vì nó không rõ ràng do vùng chết tạm thời.
Bình Estus

1
Tôi đã định hỏi về việc tham chiếu một định nghĩa let trong một hàm được khai báo trước lệnh let (tức là một bao đóng). Tôi nghĩ rằng điều này trả lời câu hỏi, nó hợp pháp nhưng sẽ là một lỗi ref nếu hàm được gọi trước khi câu lệnh let được thực thi và sẽ ổn nếu hàm được gọi sau đó. có lẽ điều này có thể được thêm vào câu trả lời nếu đúng?
Mike Lippert

2
@MikeLippert Vâng, đúng vậy. Bạn không được gọi hàm truy cập biến trước khi nó được khởi tạo. Kịch bản này xảy ra với mỗi khai báo hàm hoisted, ví dụ.
Bergi

1
Quyết định để làm constnhư letlà một lỗ hổng thiết kế. Trong một phạm vi, đáng constlẽ phải được thực hiện để được nâng lên và chỉ trong thời gian khởi tạo khi nó được truy cập. Thực sự, họ nên có một const, a letvà một từ khóa khác tạo ra một biến hoạt động như "chỉ đọc" let.
Pacerier

1
" Cái trước được khởi tạo với không xác định " có thể được chấp nhận cho khai báo var nhưng có vẻ không phù hợp với khai báo hàm, được gán một giá trị trước khi bắt đầu thực thi.
RobG

87

Trích dẫn thông số kỹ thuật của ECMAScript 6 (ECMAScript 2015) letconst phần khai báo ,

Các biến được tạo khi Môi trường từ điển chứa của chúng được khởi tạo nhưng có thể không được truy cập theo bất kỳ cách nào cho đến khi đánh giá LexicalBinding của biến .

Vì vậy, để trả lời câu hỏi của bạn, có, letconstPalăng nhưng bạn không thể truy cập chúng trước khi khai báo thực tế được đánh giá trong thời gian chạy.


22

ES6giới thiệu Letcác biến đi kèm với block level scoping. Cho đến khi ES5chúng tôi không có block level scoping, vì vậy các biến được khai báo bên trong một khối luôn nằm trong hoistedphạm vi mức chức năng.

Về cơ bản Scopeđề cập đến nơi trong chương trình của bạn, các biến của bạn được hiển thị, xác định nơi bạn được phép sử dụng các biến bạn đã khai báo. Trong ES5chúng tôi có global scope,function scope and try/catch scope, với ES6chúng tôi cũng có được phạm vi cấp khối bằng cách sử dụng Let.

  • Khi bạn xác định một biến với vartừ khóa, nó sẽ biết toàn bộ hàm từ thời điểm nó được xác định.
  • Khi bạn xác định một biến với letcâu lệnh, nó chỉ được biết đến trong khối mà nó được xác định.

     function doSomething(arr){
         //i is known here but undefined
         //j is not known here
    
         console.log(i);
         console.log(j);
    
         for(var i=0; i<arr.length; i++){
             //i is known here
         }
    
         //i is known here
         //j is not known here
    
         console.log(i);
         console.log(j);
    
         for(let j=0; j<arr.length; j++){
             //j is known here
         }
    
         //i is known here
         //j is not known here
    
         console.log(i);
         console.log(j);
     }
    
     doSomething(["Thalaivar", "Vinoth", "Kabali", "Dinesh"]);
    

Nếu bạn chạy mã, bạn có thể thấy biến jchỉ được biết trong loopvà không phải trước và sau. Tuy nhiên, biến của chúng tôi iđược biết đến entire functiontừ thời điểm nó được xác định trở đi.

Có một lợi thế lớn khác khi sử dụng let vì nó tạo ra một môi trường từ vựng mới và cũng liên kết giá trị mới hơn là giữ một tham chiếu cũ.

for(var i=1; i<6; i++){
   setTimeout(function(){
      console.log(i);
   },1000)
}

for(let i=1; i<6; i++){
   setTimeout(function(){
      console.log(i);
   },1000)
}

forVòng lặp đầu tiên luôn in giá trị cuối cùng , với letnó tạo ra một phạm vi mới và liên kết các giá trị mới in chúng tôi 1, 2, 3, 4, 5.

Về constantscơ bản, nó hoạt động cơ bản như thế let, sự khác biệt duy nhất là giá trị của chúng không thể thay đổi. Trong hằng số đột biến được cho phép nhưng không được phép gán lại.

const foo = {};
foo.bar = 42;
console.log(foo.bar); //works

const name = []
name.push("Vinoth");
console.log(name); //works

const age = 100;
age = 20; //Throws Uncaught TypeError: Assignment to constant variable.

console.log(age);

Nếu một hằng đề cập đến một object, nó sẽ luôn luôn đề cập đến objectnhưng objectchính nó có thể được thay đổi (nếu nó có thể thay đổi). Nếu bạn muốn có một bất biến object, bạn có thể sử dụngObject.freeze([])


5

Từ các tài liệu web MDN:

Trong ECMAScript 2015, letconstđược nâng lên nhưng không được khởi tạo. Tham chiếu biến trong khối trước khi khai báo biến dẫn đến kết quả là ReferenceErrorvì biến nằm trong "vùng chết tạm thời" từ khi bắt đầu khối cho đến khi khai báo được xử lý.

console.log(x); // ReferenceError
let x = 3;

0

trong es6 khi chúng ta sử dụng let hoặc const chúng ta phải khai báo biến trước khi sử dụng chúng. ví dụ. 1 -

// this will work
u = 10;
var u;

// this will give an error 
k = 10;
let k;  // ReferenceError: Cannot access 'k' before initialization.

ví dụ. 2-

// this code works as variable j is declared before it is used.
function doSmth() {
j = 9;
}
let j;
doSmth();
console.log(j); // 9
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.