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ị.
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
.
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ó.
let
và const
Mặc dù các biến khai báo bằng let
và const
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.
let
:console.log(y); // ReferenceError: Cannot access 'y' before initialization let y = 10;
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”).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.
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ã.
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ã.
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.
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ã.
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.
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 let
và const
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).
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.
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ó.
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ị.let
và const
: 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).