6.1 Khái niệm về đóng gói
Đóng gói là một trong những khái niệm mạnh mẽ và quan trọng nhất trong JavaScript. Nó cho phép các hàm ghi nhớ môi trường từ vựng của mình ngay cả sau khi chúng đã hoàn thành. Trong bài giảng này, chúng ta sẽ xem xét khái niệm đóng gói, những đặc điểm của chúng và đưa ra nhiều ví dụ về cách sử dụng chúng.
Đóng gói (closure) trong JavaScript — là sự kết hợp giữa một hàm và môi trường từ vựng, nơi mà hàm đó được khai báo. Đóng gói cho phép hàm "ghi nhớ" và truy cập vào các biến và các hàm khác từ phạm vi bên ngoài của nó, ngay cả sau khi hàm bên ngoài đó đã hoàn thành.
Những đặc điểm chính của đóng gói:
- Môi trường từ vựng: ngữ cảnh nơi hàm được khai báo, bao gồm tất cả các biến có sẵn tại thời điểm khai báo.
- Lưu trữ ngữ cảnh: hàm với đóng gói có thể giữ quyền truy cập vào các biến từ phạm vi bên ngoài của nó ngay cả sau khi hàm bên ngoài đã hoàn thành.
6.2 Ví dụ về hoạt động của đóng gói
Ví dụ 1: Đóng gói đơn giản
Trong ví dụ này, innerFunction()
có quyền truy cập vào biến outerVariable
từ phạm vi bên ngoài của nó ngay cả sau khi outerFunction()
hoàn thành.
function outerFunction() {
let outerVariable = 'I am from the outer function';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const closure = outerFunction();
closure(); // Sẽ hiển thị: I am from the outer function
Ví dụ 2: Bộ đếm sử dụng đóng gói
Trong ví dụ này, hàm bộ đếm lưu trữ giá trị của biến count
và tăng nó mỗi khi được gọi.
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // Sẽ hiển thị: 1
console.log(counter()); // Sẽ hiển thị: 2
console.log(counter()); // Sẽ hiển thị: 3
Ví dụ 3: Đóng gói trong vòng lặp
Đóng gói thường được sử dụng để lưu giữ giá trị của các biến trong vòng lặp.
Trong ví dụ này, mỗi hàm bên trong mảng arr
"ghi nhớ" giá trị của biến i
tại thời điểm nó được tạo ra nhờ vào phạm vi khối của let
:
function createArrayWithClosures() {
let arr = [];
for (let i = 0; i < 3; i++) {
arr[i] = function() {
console.log(i);
};
}
return arr;
}
const closures = createArrayWithClosures();
closures[0](); // Sẽ hiển thị: 0
closures[1](); // Sẽ hiển thị: 1
closures[2](); // Sẽ hiển thị: 2
6.3 Các kịch bản phức tạp của việc sử dụng đóng gói
Ví dụ 1: Áp dụng hàm một phần (partial application)
Đóng gói cho phép tạo ra các hàm được áp dụng một phần, giữ một số đối số cố định.
Trong ví dụ này, hàm multiply()
trả về một hàm, hàm này nhân đối số b
mà nó nhận với đối số a
đã được cố định.
function multiply(a) {
return function(b) {
return a * b;
};
}
const double = multiply(2);
console.log(double(5)); // Sẽ hiển thị: 10
console.log(double(10)); // Sẽ hiển thị: 20
Ví dụ 2: Giấu dữ liệu (data hiding)
Đóng gói có thể được sử dụng để tạo ra các biến và phương thức riêng tư.
Trong ví dụ này, các biến _name
và _age
là riêng tư và chỉ có thể truy cập thông qua các phương thức đối tượng:
function createPerson(name, age) {
let _name = name;
let _age = age;
return {
getName: function() {
return _name;
},
getAge: function() {
return _age;
},
setName: function(newName) {
_name = newName;
},
setAge: function(newAge) {
_age = newAge;
}
};
}
const person = createPerson('John', 30);
console.log(person.getName()); // Sẽ hiển thị: John
person.setName('Jane');
console.log(person.getName()); // Sẽ hiển thị: Jane
console.log(person.getAge()); // Sẽ hiển thị: 30
Ví dụ 3: Memoization
Memoization — là kỹ thuật tối ưu hóa, trong đó kết quả của hàm được lưu trữ để tránh việc tính toán lại nhiều lần cho cùng một dữ liệu đầu vào.
Trong ví dụ này, hàm memoize()
sử dụng đóng gói để lưu trữ cache của các kết quả đã được tính toán của hàm fn()
:
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
function slowFunction(num) {
console.log('Computing...');
return num * 2;
}
const memoizedFunction = memoize(slowFunction);
console.log(memoizedFunction(5)); // Sẽ hiển thị: Computing... 10
console.log(memoizedFunction(5)); // Sẽ hiển thị: 10 (kết quả lấy từ cache)
GO TO FULL VERSION