Hoisting trong JavaScript

Hoisting là một cơ chế trong JavaScript, trong đó các khai báo của biến và hàm được “đưa lên” đầu phạm vi hiện tại (scope) trong quá trình biên dịch, trước khi bất kỳ đoạn mã nào được thực thi. Điều này có nghĩa là bạn có thể sử dụng biến hoặc gọi hàm trước khi chúng được khai báo trong mã của bạn.

Tuy nhiên, điều quan trọng cần lưu ý là chỉ có phần khai báo của biến hoặc hàm được hoisting, không phải phần gán giá trị.

Hoisting với Biến

Trong JavaScript, các biến được hoisting, nhưng giá trị của chúng không được khởi tạo cho đến khi thực hiện phép gán. Trước khi giá trị được gán, biến sẽ có giá trị là undefined.

Ví dụ với var:

console.log(x); // undefined
var x = 5;
console.log(x); // 5

Ở đây, đoạn mã được hoisting như sau:

var x;            // Khai báo biến x được hoisting lên đầu
console.log(x);   // undefined (do x chưa được gán giá trị)
x = 5;            // Giá trị 5 được gán cho x
console.log(x);   // 5

Như bạn thấy, biến x được hoisting lên đầu và có giá trị undefined trước khi giá trị 5 được gán cho nó.

Hoisting với letconst

Mặc dù các biến khai báo bằng letconst cũng được hoisting, chúng có một đặc điểm khác biệt là chúng không thể được truy cập trước khi được khai báo. Điều này được gọi là “temporal dead zone” (khoảng chết tạm thời). Nếu bạn cố truy cập vào biến trước khi nó được khai báo, một lỗi ReferenceError sẽ xảy ra.

Ví dụ với let:

console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
  • Mặc dù biến y được hoisting, nhưng bạn không thể truy cập vào nó trước khi nó được khởi tạo (trong khoảng “temporal dead zone”).

Ví dụ với const:

console.log(z); // ReferenceError: Cannot access 'z' before initialization
const z = 15;

Tương tự như let, biến const cũng bị hoisting nhưng không thể sử dụng trước khi được khởi tạo. Điểm khác biệt ở đây là const yêu cầu phải được gán giá trị ngay tại thời điểm khai báo.

Hoisting với Hàm

Các hàm khai báo (function declaration) được hoisting đầy đủ, bao gồm cả phần khai báo lẫn phần định nghĩa hàm. Điều này có nghĩa là bạn có thể gọi hàm trước khi nó được định nghĩa trong mã.

Ví dụ với function declaration:

foo(); // "Hello, world!"

function foo() {
    console.log("Hello, world!");
}

Trong ví dụ này, hàm foo được hoisting lên đầu và có thể được gọi trước khi nó được định nghĩa trong mã.

Ví dụ với function expression:

bar(); // TypeError: bar is not a function

var bar = function() {
    console.log("This is a function expression");
};

Trong trường hợp này, chỉ có biến bar được hoisting, nhưng không có phần định nghĩa hàm nào được hoisting. Do đó, tại thời điểm bar() được gọi, giá trị của bar vẫn là undefined, và việc gọi nó sẽ gây ra lỗi.

Hoisting và Function Declaration vs. Function Expression

Function Declaration:

foo(); // Works fine

function foo() {
    console.log("Function declaration");
}

Với function declaration, cả phần khai báo lẫn phần định nghĩa hàm đều được hoisting. Do đó, bạn có thể gọi hàm foo() trước khi hàm được khai báo trong mã.

Function Expression:

bar(); // TypeError: bar is not a function

var bar = function() {
    console.log("Function expression");
};

Với function expression, chỉ có biến bar được hoisting (với giá trị undefined), còn phần định nghĩa hàm thì không. Do đó, khi bạn cố gắng gọi bar() trước khi hàm được gán, bạn sẽ gặp lỗi.

Hoisting trong Khối (Block Scope)

Các khai báo bên trong khối (block) như if, for, và while tuân theo quy tắc hoisting. Tuy nhiên, khi bạn sử dụng letconst trong khối, chúng không được hoisting ra khỏi phạm vi của khối đó, còn var thì vẫn được hoisting lên phạm vi hàm (function scope) hoặc phạm vi toàn cục (global scope).

Ví dụ với var trong khối:

if (true) {
    var x = 10;
}
console.log(x); // 10

Trong trường hợp này, biến x được hoisting ra ngoài phạm vi của khối và có thể truy cập ở bên ngoài.

Ví dụ với let trong khối:

if (true) {
    let y = 20;
}
console.log(y); // ReferenceError: y is not defined

Ở đây, biến y không thể truy cập bên ngoài khối vì let chỉ có hiệu lực trong phạm vi của khối chứa nó.

Tổng kết

  • Biến khai báo bằng var: Được hoisting lên đầu phạm vi (scope), nhưng giá trị của chúng là undefined cho đến khi thực sự được gán giá trị.
  • Biến khai báo bằng letconst: Cũng được hoisting, nhưng không thể sử dụng trước khi được khởi tạo (trong khoảng temporal dead zone).
  • Hàm khai báo (function declaration): Được hoisting đầy đủ, bao gồm cả phần khai báo và định nghĩa hàm.
  • Hàm biểu thức (function expression): Chỉ có biến chứa hàm được hoisting, không phải phần định nghĩa hàm.