Explore all courses in this category.
Explore all courses in this category.
Nguyễn Đình Cường
0 comment
09 Nov, 2026
Closure là khái niệm thường xuất hiện trong phỏng vấn JavaScript và khiến nhiều người mới thấy mơ hồ. Thực ra bạn đã dùng closure hằng ngày mà không biết: mỗi khi viết một callback dùng biến bên ngoài, đó là closure đang làm việc. Bài này giải thích bằng ví dụ thay vì định nghĩa khô khan.
Closure là một hàm cùng với môi trường mà nó được tạo ra. Nói cách khác, một hàm "nhớ" các biến ở phạm vi bên ngoài nơi nó được định nghĩa, ngay cả khi hàm bên ngoài đã chạy xong. Cách hình dung quen thuộc: hàm mang theo một chiếc ba lô chứa những biến nó cần.
function taoBoDem() {
let dem = 0;
return function () {
dem++;
return dem;
};
}
const boDem = taoBoDem();
console.log(boDem()); // 1
console.log(boDem()); // 2
console.log(boDem()); // 3
const boDem2 = taoBoDem();
console.log(boDem2()); // 1, độc lập với boDemKhi taoBoDem() chạy xong, thông thường biến dem sẽ biến mất. Nhưng hàm bên trong vẫn tham chiếu đến nó, nên JavaScript giữ biến đó lại. Mỗi lần gọi taoBoDem tạo ra một biến dem riêng, vì thế hai bộ đếm không ảnh hưởng nhau.
JavaScript trước đây không có thuộc tính private thực sự, closure là cách phổ biến để giấu dữ liệu. Ví dụ ví tiền nhỏ chỉ cho phép thao tác qua các hàm:
function taoVi(soDuBanDau) {
let soDu = soDuBanDau;
return {
nap(tien) { soDu += tien; },
rut(tien) {
if (tien > soDu) throw new Error('Không đủ tiền');
soDu -= tien;
},
xem() { return soDu; },
};
}
const vi = taoVi(100);
vi.nap(50);
console.log(vi.xem()); // 150
console.log(vi.soDu); // undefined, không truy cập trực tiếp đượcBên ngoài không thể sửa soDu trực tiếp, chỉ qua ba phương thức. Ngày nay JavaScript đã có trường private của class với tiền tố #, nhưng closure vẫn là nền tảng bạn cần hiểu.
Closure cho phép sản xuất các hàm chuyên biệt từ một khuôn:
function nhanVoi(heSo) {
return (x) => x * heSo;
}
const gapDoi = nhanVoi(2);
const gapBa = nhanVoi(3);
console.log(gapDoi(10)); // 20
console.log(gapBa(10)); // 30Mỗi hàm trả về nhớ giá trị heSo của riêng nó. Kỹ thuật này gọi là currying hoặc partial application ở dạng đơn giản, và là cơ sở của nhiều mẫu thiết kế, từ debounce đến middleware.
Đoạn code sau thường gây bất ngờ:
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
// in ra: 4 4 4Vì var có phạm vi theo hàm, cả ba callback chia sẻ cùng một biến i, và khi chúng chạy thì vòng lặp đã kết thúc với i bằng 4. Sửa bằng let, vì let tạo biến mới cho mỗi vòng lặp:
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
// in ra: 1 2 3Ví dụ này cho thấy closure giữ tham chiếu đến biến, không phải bản sao của giá trị tại thời điểm tạo hàm.
useState và useEffect hoạt động dựa trên closure, nên bạn gặp lỗi "giá trị cũ" (stale closure) khi hàm giữ lại biến của lần render trước.Vì closure giữ các biến sống lâu, nếu chúng tham chiếu đến đối tượng lớn (như phần tử DOM hay mảng khổng lồ) mà bạn không còn cần, bộ nhớ không được giải phóng. Hãy gỡ trình lắng nghe sự kiện khi không dùng nữa và tránh giữ tham chiếu thừa. Phần lớn trường hợp thông thường thì không cần lo, đừng tối ưu sớm.
Hãy đoán kết quả của đoạn mã dưới đây trước khi chạy:
function tao() {
let n = 10;
return {
tang: () => ++n,
xem: () => n,
};
}
const a = tao();
a.tang();
a.tang();
console.log(a.xem()); // ?
console.log(tao().xem()); // ?Đáp án là 12 và 10. Hai hàm tang và xem trong cùng một đối tượng chia sẻ chung một biến n, còn mỗi lần gọi tao() lại sinh ra một biến n mới. Nếu bạn trả lời đúng cả hai, bạn đã nắm được ý chính của closure rồi.
Closure đơn giản là hàm nhớ môi trường nơi nó được tạo ra. Hãy tự gõ lại ví dụ bộ đếm và ví dụ vòng lặp, rồi thử biến tấu. Khi tự đoán được kết quả trước khi chạy, bạn đã thật sự hiểu closure.
Nếu bạn muốn ôn JavaScript kỹ càng để tự tin hơn khi đi phỏng vấn, hãy tham khảo các khóa học trên codebem.com.
Nguyễn Đình Cường
0 comment