Hoisting Trong JavaScript: Cơ Chế Bí Ẩn Mà Mọi Dev Đều Phải Hiểu
Bạn từng thấy code JavaScript gọi một hàm ở dòng trên cùng, trong khi định nghĩa hàm đó lại nằm tận cuối file — nhưng chương trình vẫn chạy bình thường? Hay bạn truy cập một biến trước khi khai báo và nhận được undefined thay vì báo lỗi? Đó là dấu hiệu của Hoisting. Nắm hiểu cơ chế này không chỉ giúp bạn xử lý tốt các câu hỏi phỏng vấn kỹ thuật mà còn là chìa khóa để hiểu cách JavaScript engine thực sự hoạt động bên dưới — trước khi thực thi bất kỳ dòng code nào.

Trung Vũ Hoàng
Tác giả
1. Hoisting Thực Sự Là Gì?
Nhiều tài liệu mô tả Hoisting là "đưa khai báo lên đầu file". Đây là cách diễn đạt dễ hiểu nhưng không hoàn toàn chính xác — không có dòng code nào thực sự bị di chuyển. Điều thực sự xảy ra là: trong giai đoạn khởi tạo (Creation Phase), trình thông dịch JavaScript quét qua mã nguồn, tìm tất cả khai báo biến và hàm, rồi cấp phát bộ nhớ cho chúng — trước khi bắt đầu thực thi.
Điều này tạo ra "ảo giác" rằng mọi khai báo đều sẵn sàng từ đầu phạm vi (scope). Nếu không hiểu rõ điều này, bạn rất dễ gặp các lỗi logic âm thầm — đặc biệt trong codebase nhiều người cùng làm việc với phong cách viết code khác nhau.
2. var, let, const — Hoisting Hoạt Động Khác Nhau Như Thế Nào?
Đây là điểm mấu chốt cần phân biệt rõ:
var — Hoisted và khởi tạo sẵn undefined
Khi khai báo với var, biến được hoisted và được gán giá trị mặc định là undefined ngay từ đầu. Bạn có thể truy cập biến trước dòng khai báo mà không bị crash — nhưng nhận về undefined, dễ gây nhầm lẫn.
let và const — Hoisted nhưng vào Temporal Dead Zone (TDZ)
let và const (ES6+) cũng được hoisted, nhưng không được khởi tạo giá trị. Chúng rơi vào trạng thái gọi là Temporal Dead Zone (TDZ) — khoảng thời gian từ khi block bắt đầu cho đến khi dòng khai báo được thực thi. Truy cập biến trong vùng này sẽ ném ra ReferenceError ngay lập tức.
Đây chính là lý do let và const được coi là "an toàn hơn" — chúng buộc bạn phải khai báo trước khi dùng, làm code minh bạch và dễ đọc hơn hẳn.
3. Hoisting Với Hàm: Function Declaration vs Expression
Function Declaration — Hoisted hoàn toàn
Hàm khai báo theo cú pháp truyền thống (function tenHam() {}) được hoisted toàn bộ — cả tên lẫn nội dung hàm đều vào bộ nhớ ngay từ đầu. Bạn có thể gọi hàm từ bất kỳ đâu trong scope, kể cả trước dòng định nghĩa.
Function Expression — Theo quy tắc của biến
Nếu gán hàm vào biến (const fn = function() {} hoặc var fn = function() {}), quy tắc hoisting của biến đó được áp dụng. Với var, biến sẽ là undefined và gọi nó sớm sẽ gây lỗi "is not a function". Với const, sẽ nhận ReferenceError.
4. Tại Sao JavaScript Lại Có Cơ Chế Này?
Hoisting không phải là một thiếu sót ngẫu nhiên — nó có lý do thiết kế rõ ràng. Tính năng quan trọng nhất: hỗ trợ đệ quy tương hỗ (mutual recursion). Nếu hàm A gọi hàm B và hàm B gọi lại hàm A, cả hai cần "biết" về sự tồn tại của nhau. Hoisting cho phép điều đó bất kể thứ tự khai báo trong file.
Bên cạnh đó, nó phản ánh triết lý ban đầu của JavaScript: cố gắng chạy được thay vì dừng lại ngay khi gặp sự cố nhỏ. Tuy nhiên trong lập trình hiện đại, chúng ta ưu tiên sự kỷ luật và tường minh hơn là sự "linh hoạt" dễ gây lỗi này.
5. Ví Dụ Thực Tế và Phân Tích Lỗi
console.log(name); // undefined — không phải lỗi!
var name = "JavaScript";
console.log(name); // "JavaScript"Trình thông dịch thực sự xử lý như sau: var name; được hoisted lên đầu (giá trị mặc định undefined), sau đó console.log đầu tiên chạy và thấy undefined, cuối cùng mới đến dòng gán giá trị. Thay var bằng let sẽ gây ReferenceError ngay dòng đầu.
Một cạm bẫy phổ biến khác: var không có block scope. Biến khai báo bằng var bên trong if hay for sẽ "thoát" ra ngoài block, tồn tại ở phạm vi hàm hoặc toàn cục — nguyên nhân của vô số bug trong các vòng lặp xử lý bất đồng bộ.
6. Best Practices — Sống Tốt Với Hoisting
Quên var đi, dùng let và const: Đây là quy tắc số một.
constcho những gì không cần gán lại,letcho phần còn lại.Khai báo biến ở đầu scope: Dù đã dùng
let/const, thói quen khai báo biến ở đầu hàm giúp người đọc code dễ nắm bắt toàn bộ "nguyên liệu" của một logic.Tránh đặt tên biến trùng tên hàm: Đây là nguồn gốc của những bug khó hiểu và khó tái tạo nhất.
Bật Strict Mode: Thêm
"use strict";ở đầu file để ngăn JavaScript tự động tạo biến toàn cục khi bạn quên từ khóa khai báo.
Liên hệ với Closure: Hoisting là bước chuẩn bị cho Lexical Environment — môi trường mà Closure sẽ "đóng gói" và ghi nhớ sau này. Hiểu Hoisting là bước đệm tự nhiên để hiểu sâu Closure.
7. Tổng Kết
Hoisting không phải là lỗi thiết kế — đó là một tính năng có chủ đích. Khi bạn hiểu rõ giai đoạn Creation Phase và Execution Phase, bạn hiểu thực sự JavaScript vận hành như thế nào — không phải qua những giải thích bề mặt.
Code sạch không phải là code sử dụng những mẹo hóc búa. Đó là code mà bất kỳ đồng nghiệp nào cũng đọc và hiểu được ngay lập tức. Hãy dùng kiến thức về Hoisting để viết code tường minh hơn — không phải để che giấu sự phức tạp.
Bài viết liên quan

Closure Trong JavaScript: Hiểu Tường Tận Để Lập Trình Như Chuyên Gia
Closure — hay còn gọi là "hàm đóng" — là một trong những khái niệm quyền năng và cũng dễ gây bối rối nhất trong JavaScript. Nắm vững Closure không chỉ giúp bạn vượt qua phỏng vấn kỹ thuật cấp Senior mà còn thay đổi cách bạn kiến trúc toàn bộ codebase: gọn hơn, an toàn hơn, và ít bug hơn. Nhiều người hay nhầm lẫn giữa Scope và Closure, nhưng thực ra chúng có quan hệ chặt chẽ và bổ trợ cho nhau. Hãy cùng khám phá từ nền tảng.

Node trong blockchain là gì?
Tìm hiểu Node trong blockchain là gì, vai trò cốt lõi với SME. Hướng dẫn 2026: phân loại, chi phí, triển khai, ứng dụng SEO/Marketing và bảo mật.

Blockchain Bất Biến: Lý Do Và Ứng Dụng Thực Tiễn 2026
Khám phá vì sao dữ liệu blockchain không thể sửa, cơ chế bảo toàn dữ liệu và ứng dụng cho SME trong Digital Marketing, SEO để tăng niềm tin và ROI.