JavaScript cho phép chúng ta khai báo hàm nằm bên trong hàm. Nếu như hàm bên trong tham chiếu đến hoặc sử dụng biến của lexical environment, thì hàm đó được gọi là closure.
Về mặt ngữ nghĩa, closure có nghĩa là tính khép kín, ý nói rằng function scope là một phạm vi mà ở đó mọi tham chiếu chỉ có thể xảy ra bên trong hàm đó mà thôi.
Ví dụ:
// Outer function
const cube = (n) => {
// -> inside cube function scope <-
// Inner function
const square = () => {
// Reference to outer function variable, not copy
return n * n
}
return square() * n
}
console.log(cube(3)) // 27Example
Ta có thể tạo ra một chương trình để đếm đơn giản như sau:
function createCounter() {
// -> inside createCounter function scope <-
let count = 0
function increase() {
return ++count
}
return increase
}
// Only one function scope is created
const counter = createCounter()Khi hàm createCounter được gọi, một function scope của hàm này được tạo ra kèm theo biến count và hàm increase. Hàm increase do tham chiếu đến biến count bên ngoài nên được gọi là hàm closure, và đồng thời nó cũng lưu vị trí mà nó được tạo ra (bên trong hàm createCounter).
Hàm increase sau đó được gán cho biến counter, là một biến có global scope.
console.log(counter()) // 1
console.log(counter()) // 2
console.log(counter()) // 3Khi gọi hàm counter, ta tăng biến count lên một giá trị. Do chỉ có duy nhất một function scope được tạo ra, lời gọi hàm thứ hai và thứ ba vẫn sẽ tham chiếu đến biến count ở phạm vi cũ.
Nếu ta gọi hàm createCounter một lần nữa, sẽ có một function scope mới được tạo ra, đồng nghĩa với việc bộ đếm sẽ đếm lại từ đầu, tương ứng với phạm vi hàm mới.
Do bị tham chiếu bởi một closure, vùng nhớ của biến count sẽ không bị hủy khi lời gọi hàm createCounter được kết thúc. Thêm vào đó, do nằm bên trong biến counter có global scope, count sẽ tồn tại đến khi kết thúc chương trình.
Encapsulation in JavaScript
Có thể dùng closure để biểu diễn và ứng dụng tính đóng gói (Encapsulation) trong OOP.
Xét ví dụ sau:
function PersonAccount() {
let name = ""
let age
function getName() {
return name
}
function setName(newName) {
name = newName
}
return {
getName,
setName,
}
}
const account = PersonAccount()
account.setName("Quân")
console.log(account.getName()) // "Quân"Có thể thấy:
- Các biến bên trong hàm
PersonAccountlà các thuộc tính private lưu trữ dữ liệu mà ta muốn bảo vệ. - Các hàm trả về của
PersonAccountlà các phương thức của object và có thể truy cập được thông qua object trả về (account).