Javascript

VAR, LET và CONST - Hoisting và Scope trong JavaScript

VAR, LET và CONST

JavaScript, cũng như các ngôn ngữ lập trình khác, cho phép khai báo biến bằng nhiều cách khác nhau. Với các keyword var, letconst và mỗi keyword lại có use case khác nhau, bài viết này sẽ tập trung vào những điều cần cân nhắc khi sử dụng các phương thức khai báo biến này Hãy cùng xem qua định nghĩa của từng keyword, dựa vào trang chính thức của ECMAScriptMDN

Var khai báo một biến có phạm vi là execution context hiện tại của nó , có thể khởi tạo giá trị hoặc không.

Let khai báo một biến có phạm vi trong một block (có thể hiểu là giữa hai dấu {, }, có thể khởi tạo giá trị hoặc không.

Const khai báo một hằng số có phạm vi trong một block giống như biến khai báo bằng let nhưng giá trị của hằng số không thể thay đổi. Khai báo bằng const tạo ra một tham chiếu read-only tới giá trị.

HOISTING

Về lý thuyết, có thể hiểu là hoisting sẽ đưa các khai báo biến và phương thức lên trên đầu code của bạn. Nhưng điều thực sự xảy ra là các khai báo biến và hàm được đưa vào bộ nhớ trước trong quá trình biên dịch và chúng vẫn giữ nguyên vị trí trong code. Một vài điều đáng lưu ý rút ra từ định nghĩa của hoisting là:

  • Chỉ duy nhất việc khai báo biến và hàm được di chuyển. Viện gán hay khởi tạo biến không bao giờ di chuyển.
  • Chính xác thì các khai báo không bị chuyển lên đầu code, mà thay vào đó là thêm vào bộ nhớ trước.

Trong JavaScript, tất cả các định nghĩa biến với keyword var có giá trị ban đầu là undefined. Đó là bởi vì hoisting đã đưa các khai báo biến vào bộ nhớ và khởi tạo chúng với giá trị undefined. Hãy xét ví dụ sau

   console.log(x) // prints undefined
   console.log(y) // throws ReferenceError: y is not defined
   var x = 1;

Ngược lại, định nghĩa biến với keyword let hoặc const khi được hoist không khởi tạo với giá trị undefined. Thay vào đó, chúng sẽ có trạng thái gọi là Temporal Dead Zone và không được khởi tạo tới khi câu lệnh định nghĩa chúng được chạy tới.

console.log(x); // throws TDZ ReferenceError: x is not defined
let x = 1;

hoặc

var x = 10;
{
    console.log(x); // throws TDZ ReferenceError: x is not defined
    let x = 5;
}

Biến x được định nghĩa bằng keyword let trong block được hoist và ưu tiên hơn biến x được định nghĩa bằng var. Hơn nữa, nó vẫn đang trong Temporal Dead Zone khi được tham chiếu trong console.log(x), vậy nên throw một reference error

SCOPE

Biến định nghĩa với keyword var có phạm vi là execution context hiện tại của nó. Chúng không phải là phạm vi block vậy nên có thể được truy cập từ ngoài block mà nó được định nghĩa, chỉ cần nó vẫn nằm trong phạm vi của execution context. Ngược lại, biến định nghĩa bằng let hoặc const có phạm vi là block, có nghĩa không thể truy cập được từ ngoài block. Để dễ hiểu hơn hãy xét ví dụ sau:

(function () {
    {
        var x = 2;
        let y = 3;
        const z = 4;
    }
    if (true) {
        console.log(x); // prints 2
        console.log(y); // throws ReferenceError - out of its scope
        console.log(z); // throws ReferenceError - out of its scope
    }
    consolt.log(x) // prints 2
    console.log(y); // throws ReferenceError - out of its scope
    console.log(z); // throws ReferenceError - out of its scope
})();
console.log(x); // throws ReferenceError - out of its scope

Lưu ý, khi bạn khai báo biến global với keyword var, biến được gắn vào global context (window trong trình duyệt và global trong node). Điều này không đúng với trường hợp khai báo biến global với letconst

Lưu ý

Khi bạn không khai báo biến nhưng gán giá trị cho biến, biến sẽ được tạo và gắn vào execution context hiện tại của nó (giá trị hiện tại của this). Tuy nhiên, điều này không được khuyển khích bởi nó làm việc debug khó hơn rất nhiều

x = "this gets attached to the global this";
console.log(this.x); // prints the value of x
function testFn() {
    y = "this get attached to the this of the function";
    console.log(this.y); //prints the value of y
}
testFn();

Biến khai báo bằng keyword var có thể khai báo lại bất cứ lúc nào, khác với letconst chỉ có thể khai báo 1 lần trong phạm vi (scope) của nó

var x = 1;
var x = 2;
console.log(x); // prints 2
let y = 1;
let y = 2; // throws SyntaxError: Identifier y has already been declared
const z = 1;
const z = 2; // throws SyntaxError: Identifier z has already been declared

Điều này có thể gây ra lỗi nếu bạn sử dụng let hoặc const để khai báo biến trong switch case:

var x = 1;
switch (x) {
    case 0:
            let foo = 20;
            break;
     case 1:
             let foo = 30; // throws SyntaxError: Identifier foo há already been declared
             break;
}

Tất nhiên là có thể tránh bằng cách sử dụng ngoặc nhọn để định nghĩa các block riêng biệt:

var x = 1;
switch (x) {
    case 0:
        {
            let foo = 20;
            break;
         }
     case 1:
         {
             let foo = 30;
             break;
         }
}

Một lưu ý khác là hằng số mặc dù giá trị không thể gán lại, nó vẫn có thể thay đổi. Ví dụ nếu như giá trị là một object, các thuộc tính của object đó có thể thay đổi được:

const obj = {
    firstName: "Favour"
};
obj.lastName = "Harrison";
console.log(obj); // prints an object 

Hi vọng bài viết sẽ có ích cho các bạn

Tham khảo

https://blog.usejournal.com/var-let-and-const-hoisting-and-scope-8860540031d1

Registration Login
Sign in with social account
or
Lost your Password?
Registration Login
Sign in with social account
or
A password will be send on your post
Registration Login
Registration