Hoisting nghĩa đen là “kéo lên”. Trong JavaScript, trước khi trình duyệt chạy code, trình thông dịch sẽ “kéo” các khai báo biến và hàm lên đầu phạm vi (scope).
Nói dễ hiểu hơn:
Bạn tưởng rằng code chạy từ trên xuống dưới, nhưng JavaScript trước tiên sẽ đi lén lút tìm hết các
var,functionrồi “đặt tạm” chúng lên trên đầu trong trí nhớ, rồi sau đó mới chạy code như bạn viết.
Ví dụ từng loại cho dễ hình dung
🟡 var – chỉ hoist phần khai báo, không hoist gán giá trị
console.log(a); // undefined
var a = 10;
⛳️ JavaScript hiểu thế này:
var a; // "hoisted"
console.log(a); // undefined (vì chỉ mới khai báo, chưa gán giá trị)
a = 10;
⚠️ Không lỗi, nhưng sẽ ra undefined. Đây là lý do
varđược cho là “rủi ro cao” trong code lớn.
🔴 let / const – có hoist nhưng rơi vào vùng chết TDZ
console.log(b); // ❌ ReferenceError
let b = 20;
🧨 Lý do lỗi: Tồn tại vùng tên là Temporal Dead Zone (TDZ) – vùng từ đầu block tới lúc bạn khai báo biến.
🟢 function declaration – được hoist toàn phần
sayHello(); // ✅ "Hello"
function sayHello() {
console.log("Hello");
}⛳️ JavaScript hiểu thế này:
function sayHello() {
console.log("Hello");
}
sayHello();
✅ Cả tên hàm và nội dung hàm đều được kéo lên đầu, nên gọi thoải mái trước khi khai báo.
🔴 function expression – KHÔNG hoist giá trị
greet(); // ❌ TypeError: greet is not a function
var greet = function () {
console.log("Hi");
};
⛳️ JavaScript hiểu thế này:
var greet; // hoisted, giá trị là undefined
greet(); // ❌ Lỗi vì gọi undefined như function
greet = function () { ... }
So sánh tổng kết
| Loại | Có Hoist? | Gọi trước khai báo được không? | Ghi nhớ đơn giản |
|---|---|---|---|
var | ✅ Có | ✅ Được (giá trị là undefined) | Cẩn thận vì không lỗi nhưng sai |
let / const | ✅ Có | ❌ Không (TDZ gây lỗi) | Phải khai báo trước mới dùng được |
function | ✅ Có (đầy đủ) | ✅ Được | Gọi thoải mái |
| Function expr. | ✅ Biến có | ❌ Không (gọi là lỗi) | Chỉ dùng sau khi đã gán hàm |
Kết luận dễ nhớ
varthì được hoist nhưng “nguy hiểm” → không nên dùng nữa.let,constcũng hoist nhưng bị “khóa” trong TDZ.functionthì hoist hoàn toàn – có thể gọi thoải mái.function expressionchỉ hoist cái tên biến thôi, còn giá trị (hàm) thì chưa có → không gọi được trước.
Học để làm gì?
- Hiểu rõ hơn lỗi ReferenceError, TypeError khi code không chạy.
- Viết code an toàn hơn: Luôn khai báo trước khi dùng!
- Phân biệt rõ các loại hàm và biến.
- Tránh bug khó phát hiện trong code khi dùng
var.
Ví dụ thực tế:
1. Hiểu rõ hơn lỗi ReferenceError, TypeError
Tình huống thực tế: Bạn làm một trang web và khi bấm nút thì báo lỗi
Uncaught ReferenceErrorhoặcTypeError. Bạn không hiểu vì sao.
Ví dụ minh hoạ:
document.getElementById('btn').addEventListener('click', function () {
console.log(message);
});
let message = "Xin chào!";
⛔️ Khi bạn bấm nút, lỗi hiện ra:
Uncaught ReferenceError: Cannot access 'message' before initialization
💡 Vì sao?
Biến message bị hoist, nhưng nằm trong TDZ do khai báo bằng let. Nó đã “tồn tại” nhưng bạn không được phép chạm vào trước dòng let message.
✅ Cách sửa đúng:
Khai báo trước khi dùng:
let message = "Xin chào!";
document.getElementById('btn').addEventListener('click', function () {
console.log(message);
});
🎯 2. Viết code an toàn hơn – khai báo trước khi dùng
Tình huống thực tế: Một nhóm lập trình viên cùng viết code. Một người gọi biến
countở đầu file, người khác khai báovar count = 0;ở dưới cuối. Đoạn code chạy sai mà không ai để ý.
function showCount() {
console.log(count); // ra undefined
}
showCount();
var count = 10;
Kết quả?
Bạn tưởng sẽ ra 10, nhưng thực tế ra undefined.
Vì var count được hoist lên trên đầu, nhưng phần gán
= 10thì không được hoist.
✅ Cách an toàn:
Khai báo rõ ràng trước khi dùng để tránh nhầm lẫn:
var count = 10;
function showCount() {
console.log(count); // ✅ 10
}
showCount();
🎯 3. Phân biệt rõ các loại biến và hàm
Tình huống thực tế: Bạn đang viết một module có các function. Một cái dùng
function, một cái dùngconst = () =>. Bạn gọi nhầm thứ tự và bị lỗi.
Ví dụ minh họa:
sayHi(); // ✅ chạy được
sayHello(); // ❌ lỗi
function sayHi() {
console.log("Chào bạn!");
}
const sayHello = function () {
console.log("Hello!");
};
Kết quả:
sayHi() chạy bình thường vì đó là function declaration, được hoist hoàn toàn. sayHello() là function expression, chỉ hoist cái tên biến sayHello, nhưng giá trị thì chưa được gán → TypeError.
🎯 4. Tránh bug khó phát hiện khi dùng var
Tình huống thực tế: Bạn dùng vòng lặp để gán sự kiện cho các nút. Nhưng nút nào cũng in ra số cuối cùng của vòng lặp!
for (var i = 0; i < 3; i++) {
document.getElementById("btn" + i).addEventListener("click", function () {
console.log("Bạn bấm nút số: ", i);
});
}
⛔️ Dù bạn bấm btn0, btn1, hay btn2, tất cả đều in ra:
Bạn bấm nút số: 3
Vì var có phạm vi function scope, không phải block scope. Nên
ibị hoist và dùng chung cho mọi hàm.
✅ Cách sửa đúng:
Dùng let để mỗi lần lặp tạo một bản sao riêng của i:
for (let i = 0; i < 3; i++) {
document.getElementById("btn" + i).addEventListener("click", function () {
console.log("Bạn bấm nút số: ", i); // đúng số
});
}