a

20:37:35 29/3/2025 - 1 views -
JS

Closure

Closure trong JavaScript: Tổng quan toàn diện

Closure là một khái niệm quan trọng trong JavaScript và là nền tảng của nhiều tính năng mạnh mẽ trong ngôn ngữ này. Closure cho phép bạn tạo ra các hàm có khả năng "nhớ" môi trường mà chúng được tạo, ngay cả khi môi trường đó không còn tồn tại.


Định nghĩa Closure

Closure là một hàm có thể truy cập vào các biến từ phạm vi bên ngoài (outer scope), ngay cả khi hàm đó đã được thực thi hoặc môi trường bên ngoài đã kết thúc vòng đời.

Đặc điểm cơ bản:

  • Một closure bao gồm:
    • Hàm bên trong (inner function).
    • Môi trường mà hàm bên trong được tạo ra (bao gồm các biến cục bộ từ phạm vi bên ngoài).

Cách hoạt động của Closure

JavaScript sử dụng cơ chế Lexical Scoping (phạm vi tĩnh) để xác định nơi một biến có thể được truy cập. Khi một hàm được tạo ra, nó sẽ "nhớ" tất cả các biến từ phạm vi chứa nó, kể cả sau khi hàm bên ngoài đã hoàn thành.

Ví dụ đơn giản:

function outerFunction() {
    let outerVariable = "I'm outside!";

    function innerFunction() {
        console.log(outerVariable); // Truy cập biến từ phạm vi bên ngoài
    }

    return innerFunction;
}

const closure = outerFunction(); // Gán innerFunction vào biến closure
closure(); // Output: I'm outside!

Trong ví dụ trên:

  • innerFunction là một closure vì nó truy cập biến outerVariable từ phạm vi của outerFunction.
  • Ngay cả khi outerFunction đã hoàn thành, innerFunction vẫn có thể truy cập outerVariable.

Ứng dụng của Closure

Closure rất hữu ích trong nhiều tình huống khác nhau:


a. Tạo private variables

JavaScript không hỗ trợ khai báo private variables trực tiếp trong class (trước ES6). Tuy nhiên, bạn có thể sử dụng closure để tạo các biến riêng tư.

Ví dụ:

function createCounter() {
    let count = 0; // Biến private

    return {
        increment: function () {
            count++;
            console.log(count);
        },
        decrement: function () {
            count--;
            console.log(count);
        }
    };
}

const counter = createCounter();
counter.increment(); // Output: 1
counter.increment(); // Output: 2
counter.decrement(); // Output: 1

Ở đây:

  • Biến count không thể truy cập trực tiếp từ bên ngoài.
  • Chỉ có thể thay đổi count thông qua các phương thức incrementdecrement.

b. Factory functions

Closure có thể được sử dụng để tạo các factory functions, tức là các hàm trả về các hàm khác với các hành vi cụ thể.

Ví dụ:

function createMultiplier(multiplier) {
    return function (value) {
        return value * multiplier;
    };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15

Trong ví dụ trên:

  • createMultiplier trả về một closure ghi nhớ giá trị của multiplier.
  • Mỗi lần gọi createMultiplier, một closure mới được tạo với giá trị multiplier khác nhau.

c. Callback functions

Closure thường được sử dụng trong callback functions để duy trì trạng thái giữa các lần gọi.

Ví dụ:

function setupCounter() {
    let count = 0;

    document.getElementById("incrementBtn").addEventListener("click", function () {
        count++;
        console.log(`Count: ${count}`);
    });
}

setupCounter();

Trong ví dụ trên:

  • Closure được sử dụng để duy trì trạng thái của biến count giữa các lần nhấn nút.

d. Module pattern

Closure là nền tảng của module pattern, giúp tổ chức mã nguồn theo kiểu mô-đun, tránh xung đột tên biến và tạo private/public API.

Ví dụ:

const MyModule = (function () {
    let privateVariable = "I am private";

    function privateMethod() {
        console.log(privateVariable);
    }

    return {
        publicMethod: function () {
            privateMethod();
        }
    };
})();

MyModule.publicMethod(); // Output: I am private
console.log(MyModule.privateVariable); // Output: undefined

Cảnh báo và hạn chế

Mặc dù closure rất mạnh mẽ, nhưng cần lưu ý một số vấn đề:


a. Memory leaks

Closure có thể giữ lại các biến trong bộ nhớ ngay cả khi chúng không còn cần thiết, dẫn đến memory leaks nếu không quản lý đúng cách.

Ví dụ:

function createBigObject() {
    let bigArray = new Array(1000000).fill("data"); // Tạo một mảng lớn
    return function () {
        console.log(bigArray.length);
    };
}

const closure = createBigObject();
closure(); // Output: 1000000

Trong ví dụ trên:

  • bigArray vẫn chiếm bộ nhớ ngay cả khi createBigObject đã hoàn thành.
  • Để giải phóng bộ nhớ, bạn cần đảm bảo rằng closure không còn tham chiếu đến các biến không cần thiết.

b. Performance issues

Closure có thể làm giảm hiệu suất nếu được sử dụng quá mức trong các vòng lặp hoặc ứng dụng đòi hỏi tốc độ cao.

Ví dụ:

function createFunctions() {
    const result = [];
    for (var i = 0; i < 5; i++) {
        result.push(function () {
            console.log(i);
        });
    }
    return result;
}

const functions = createFunctions();
functions[0](); // Output: 5
functions[1](); // Output: 5

Trong ví dụ trên:

  • Tất cả các closure đều trỏ đến cùng một biến i, và giá trị cuối cùng của i5.
  • Để khắc phục, bạn có thể sử dụng let thay vì var hoặc wrap closure trong một IIFE.

Closure vs Global Scope

Closure giúp tránh việc sử dụng global scope, giúp mã nguồn sạch hơn và dễ bảo trì hơn.

Ví dụ:

// Sử dụng global scope
let counter = 0;
function increment() {
    counter++;
    console.log(counter);
}

// Sử dụng closure
function createCounter() {
    let counter = 0;
    return function () {
        counter++;
        console.log(counter);
    };
}

const increment = createCounter();
increment(); // Output: 1
increment(); // Output: 2

Closure trong thực tế

Closure được sử dụng rộng rãi trong các thư viện và framework JavaScript, chẳng hạn như:

  • Quản lý state trong React.
  • Xử lý sự kiện trong DOM.
  • Tạo các hàm debounce/throttle.
  • Triển khai currying và partial application.

Ví dụ: Debounce function

function debounce(func, delay) {
    let timeoutId;
    return function (...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func.apply(this, args), delay);
    };
}

const handleInput = debounce((value) => {
    console.log("Input:", value);
}, 300);

document.getElementById("inputField").addEventListener("input", (e) => {
    handleInput(e.target.value);
});

Tổng kết

Closure là một khái niệm mạnh mẽ trong JavaScript, cho phép bạn:

  1. Tạo private variables.
  2. Tạo factory functions.
  3. Xử lý callback functions.
  4. Triển khai module pattern.
  5. Duy trì trạng thái giữa các lần gọi hàm.

Tuy nhiên, cần lưu ý các vấn đề về bộ nhớ và hiệu suất khi sử dụng closure. Nếu được sử dụng đúng cách, closure sẽ giúp bạn viết mã nguồn gọn gàng, linh hoạt và dễ bảo trì hơn.