- vừa được xem lúc

Tìm hiểu về Closure và tính ứng dụng thực tế trong Javascript

0 0 1

Người đăng: Nguyễn Anh

Theo Viblo Asia

1. Closure là gì ?

Closure thường được dịch là "hàm đóng", "hàm khép kín" hoặc đơn giản là "hàm có vùng nhớ riêng". Tuy nhiên, để dễ hiểu hơn Closure là một cơ chế giúp một hàm con “ghi nhớ” được các biến ở phạm vi cha của nó, ngay cả khi hàm cha đã kết thúc. Vậy tại sao phải dùng closure để làm điều đó ? Vì bình thường, khi một hàm kết thúc, biến bên trong nó sẽ bị "giải phóng khỏi bộ nhớ" (GC - Garbage Collection) Nhưng nếu có một closure, tức là có một hàm con vẫn đang sử dụng biến của hàm cha, thì những biến đó sẽ được giữ lại trong bộ nhớ. Việc biến đó vẫn được giữ lại trong bộ nhớ ngay cả khi hàm cha đã kết thúc rồi sẽ là công cụ rất mạnh để xây dựng các chức năng nhớ được “lần trước” như:

  • Tạo bộ đếm
  • cache
  • debounce
  • throttle
  • Tối ưu hiệu suất: Cache kết quả để khỏi tính toán lại

Ví dụ minh họa

function createCounter() { let count = 0; return function () { count++; return count; };
} const counter = createCounter(); console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3 

Giải thích:

  • createCounter() là hàm cha, trả về một hàm con.
  • Biến count nằm trong phạm vi của hàm cha createCounter.
  • Sau khi createCounter() chạy xong, lẽ ra count phải biến mất khỏi bộ nhớ.
  • Nhưng! vì hàm con đang "đóng gói" (closure) và sử dụng count, nên count vẫn tồn tại và giữ giá trị mỗi lần gọi tiếp theo.

Vậy đến đây có thể kết luận 1 tính năng của Closure là “nhớ trạng thái” (state) qua thời gian, nhưng không làm lộ biến ra ngoài. Closure cho phép tạo ra một vùng nhớ riêng biệt, nơi có thể lưu thông tin mà chỉ có hàm con mới truy cập được, người bên ngoài không can thiệp được.

function createSecretHolder(secret) { return { getSecret: function() { return secret; }, setSecret: function(newSecret) { secret = newSecret; } };
} const holder = createSecretHolder('abc123');
console.log(holder.getSecret()); // 'abc123'
holder.setSecret('xyz789');
console.log(holder.getSecret()); // 'xyz789'

→ Ở đây, biến secret được giữ lại bởi closure và không ai bên ngoài có thể truy cập trực tiếp, chỉ có thể thông qua hàm getSecret() và setSecret() → giống như private variable trong OOP.

2. Một số ứng dụng trong các bài toán thực tế

Có vẻ vẫn khá khó hiểu đúng không, đến đây chúng ta sẽ đi vào 1 số ví dụ thực tế qua các bài toán khi code để rõ hơn nhé.

Ví dụ 1: Tạo một custom hook có thể đếm số lần người dùng click nhưng không cập nhật lại component mỗi lần (tức là state “ẩn”, không render lại).

getCounter trả về một hàm có closure ghi nhớ biến countRef, Mỗi lần click, countRef.current tăng lên, không cần re-render, Closure giúp duy trì trạng thái click mà không phụ thuộc vào React state. Điều này tránh việc re-render không cần thiết, đúng nguyên tắc sử dụng state trong react. Chúng ta dùng qua ref bản chất ở đây chính là closure.

import { useEffect, useRef } from "react"; export function useClickCounter() { const countRef = useRef(0); // không trigger re-render function getCounter() { // Đây chính là closure return function () { countRef.current += 1; console.log("Số lần click:", countRef.current); }; } return getCounter();
} // Sử dụng
import React from "react";
import { useClickCounter } from "./useClickCounter"; export default function App() { const handleClick = useClickCounter(); return ( <button onClick={handleClick}> Click me! </button> );
} 

Ví dụ 2 Debounce function trong ô tìm kiếm

Vấn đề gặp phải: Khi người dùng gõ vào ô tìm kiếm (input), ta không muốn gọi API liên tục theo từng phím gõ, vì:

  • Gây tốn tài nguyên
  • Server dễ bị quá tải
  • UX không mượt (gọi API liên tục → lag, delay)

Giải pháp: Dùng closure để debounce (chờ yên một lúc mới gọi API). Các bước như sau:

  • Người dùng gõ: "h", "he", "hel", "hell", "hello"
  • Mỗi lần debouncedSearch() được gọi, closure giữ lại timerId trước đó
  • Nếu chưa đủ 500ms mà người dùng lại gõ tiếp → timeout cũ bị huỷ
  • Chỉ khi dừng gõ 500ms → callback gọi API mới được thực thi

Closure giúp ghi nhớ timeoutId của setTimeout, để lần sau có thể huỷ lần gọi cũ, tránh gọi API thừa.

function debounce(callback, delay) { let timerId; // closure giữ lại giữa các lần gọi return function (...args) { clearTimeout(timerId); // huỷ lần trước timerId = setTimeout(() => { callback(...args); // gọi callback sau delay }, delay); };
} // Sử dụng
import React from "react"; function App() { // Callback gọi API const fetchSearchResult = (query) => { console.log("Call API with:", query); // giả lập gọi API }; // Tạo hàm debounce const debouncedSearch = React.useMemo(() => debounce(fetchSearchResult, 500), []); return ( <input type="text" placeholder="Search..." onChange={(e) => debouncedSearch(e.target.value)} /> );
}

3. So sánh Closure với Class và tính bao đóng trong OOP (encapsulation)

Lướt qua có vẻ giống giống với tính bao đóng, 1 trong 4 tính chất của OOP mà mình đã từng học ở môn lập trình OOP ở Đại học đúng không ? Closure có vẻ đã mô phỏng lại tính bao đóng (encapsulation) một cách tự nhiên trong JavaScript — vốn là ngôn ngữ hướng hàm (functional), không thuần hướng đối tượng. Closure giống như OOP nghèo — không có từ khóa private, class, nhưng vẫn giữ được nguyên tắc ẩn thông tin và kiểm soát truy cập.

Nếu bạn đang học JS, thì closure chính là cách giúp bạn áp dụng tư duy OOP một cách đơn giản mà mạnh mẽ.

Cảm ơn anh em đã đọc bài. Bài viết có sự hỗ trợ của GPT để diễn đạt câu cú cho chuẩn và đỡ phải gõ nhiều 😆😆😆

Bình luận

Bài viết tương tự

- vừa được xem lúc

Closure trong Javascript - Phần 2: Định nghĩa và cách dùng

Các bạn có thể đọc qua phần 1 ở đây. Để mọi người không quên, mình xin tóm tắt gọn lại khái niệm lexical environment:.

0 0 76

- vừa được xem lúc

Closure trong Javascript - Phần 1: Lexical Environment

Xin chào các bạn, dạo này mình vẫn đang tìm tòi Javascript các thứ, có đọc qua đến phần closure. Để hiểu được closure thì trước tiên cần biết Lexical Environment là gì, vậy hãy cùng đi tìm hiểu.

0 0 33

- vừa được xem lúc

Tìm hiểu Closures trong Javascript đơn giản nhất

Closures (tạm dịch: bao đóng) cho phép lập trình viên Javascript viết mã tốt hơn. Chúng ta thường sử dụng closures trong Javascript, dù ít hay nhiều kinh nghiệm thì chúng ta vẫn sẽ bắt gặp nó lần này

0 0 41

- vừa được xem lúc

Hiểu về Closure trong JavaScript

Nếu bạn là người mới học hoặc chưa hiểu cách thread of execution, execution context, và call stack hoạt động, bạn phải đọc bài này để có thể hoàn toàn hiểu khi đọc bài viết dưới đây. Closure là khi mộ

0 0 23

- vừa được xem lúc

Core JavaScript Engine: Cách JavaScript Thực Thi

Execution Context. Khi chương trình JavaScript được chạy, global execution context sẽ được khởi tạo, toàn bộ code ở global sẽ được thực thi trong context này.

0 0 16

- vừa được xem lúc

Cơ bản về đa luồng trong Rust (#02)

đây là phần tiếp theo về đa luồng trong Rust, bạn có thể xem lại Phần 1 ở đây hoặc xem các video diễn giải về các vấn đề liên quan ở kênh RustDEV Vietnam. Trong nhiều bài toán, khi chúng ta biết chắc

0 0 23