Closure trong TypeScript
Trong bài viết này, chúng tôi sẽ giải thích về closure trong TypeScript.
YouTube Video
Closure trong TypeScript
Closure là gì?
Closure đề cập đến khả năng giữ lại tham chiếu đến phạm vi (môi trường) nơi một hàm được định nghĩa, ngay cả khi hàm được gọi bên ngoài phạm vi đó. Bên dưới, closure sẽ được giải thích cùng với chú thích kiểu dữ liệu.
Nói một cách đơn giản, closure kết hợp một hàm với môi trường biến nơi hàm được định nghĩa, cho phép truy cập vào môi trường đó khi hàm được gọi.
Cơ chế cơ bản của Closure
Trong TypeScript, khi một hàm được định nghĩa bên trong một hàm khác, rõ ràng là hàm bên trong có thể truy cập các biến của hàm bên ngoài. Dưới đây là một ví dụ cơ bản về closure với chú thích kiểu dữ liệu.
1function outerFunction(): () => void {
2 let outerVariable: string = "I am from outer function";
3
4 function innerFunction(): void {
5 // The inner function accesses the variable of the outer function
6 console.log(outerVariable);
7 }
8
9 return innerFunction;
10}
11
12const closure: () => void = outerFunction();
13closure(); // "I am from outer function"
outerFunction
trả về hàm bên trong làinnerFunction
.innerFunction
hiển thị giá trị của biếnouterVariable
trong hàm bên ngoài`.
Ứng dụng và lợi ích của Closure
Đóng gói dữ liệu
Dưới đây là một ví dụ về closure với chú thích kiểu dữ liệu để đóng gói dữ liệu.
1function createCounter(): () => number {
2 let count: number = 0;
3
4 return function (): number {
5 count += 1;
6 return count;
7 };
8}
9
10const counter: () => number = createCounter();
11console.log(counter()); // 1
12console.log(counter()); // 2
13console.log(counter()); // 3
- Hàm
createCounter
trả về một hàm trả về một giá trị kiểunumber
. - Biến
count
được định nghĩa với kiểunumber
và được thao tác bên trong closure.
Hàm bậc cao
Closure hữu ích khi tạo ra các hàm bậc cao. Ở đây, hàm bậc cao là hàm nhận một hàm khác làm đối số hoặc trả về một hàm như là kết quả. Dưới đây là một ví dụ về hàm bậc cao với chú thích kiểu dữ liệu rõ ràng.
1function createMultiplier(multiplier: number): (value: number) => number {
2 return function (value: number): number {
3 return value * multiplier;
4 };
5}
6
7const double: (value: number) => number = createMultiplier(2);
8console.log(double(5)); // 10
9
10const triple: (value: number) => number = createMultiplier(3);
11console.log(triple(5)); // 15
createMultiplier
là một hàm bậc cao tạo ra một hàm để nhân với số mà nó nhận được làm đối số.- Hàm nhân bên trong cũng nhận một số và trả về kết quả của phép nhân dưới dạng một số.
Ví dụ về cách cài đặt closure trong TypeScript
Chúng ta cũng hãy xem một ví dụ về cách triển khai bộ đếm dựa trên phạm vi dưới dạng một closure.
1function rangeCounter(min: number, max: number): () => number | string {
2 let count: number = min;
3
4 return function (): number | string {
5 if (count <= max) {
6 return count++;
7 } else {
8 return `Count has exceeded the maximum value: ${max}`;
9 }
10 };
11}
12
13const counter: () => number | string = rangeCounter(1, 5);
14
15console.log(counter()); // 1
16console.log(counter()); // 2
17console.log(counter()); // 3
18console.log(counter()); // 4
19console.log(counter()); // 5
20console.log(counter()); // "Count has exceeded the maximum value: 5"
- Hàm
rangeCounter
trả về một hàm có thể trả vềnumber
hoặcstring
. - Trong hàm bên trong, nếu
count
vượt quámax
, nó sẽ trả về một thông báo kiểustring
; ngược lại, nó trả về kiểunumber
.
Lưu ý khi sử dụng Closure
Nguy cơ rò rỉ bộ nhớ từ Closure
Closure có thể giữ lại các biến từ phạm vi bên ngoài, đôi khi có thể gây ra rò rỉ bộ nhớ. Những closure không cần thiết cần được giải phóng khỏi bộ nhớ một cách rõ ràng.
1function createLeak(): () => void {
2 // Large array consuming significant memory
3 const largeArray: string[] = new Array(1000000).fill("leak");
4
5 // Closure capturing `largeArray`
6 return function (): void {
7 console.log(largeArray[0]); // Using the captured array
8 };
9}
10
11// Create a closure that holds a reference to the large array
12let leakyFunction = createLeak();
13
14// The large array is not released as `leakyFunction` still references it
15
16// When the object is no longer needed
17leakyFunction = null; // Explicitly remove the reference
- Trong đoạn mã này,
largeArray
được tạo bên trongcreateLeak
lẽ ra sẽ được giải phóng khi ra khỏi phạm vi, nhưng không được vì closure đã giữ lạilargeArray
. Miễn làleakyFunction
còn tồn tại, vùng bộ nhớ không cần thiết này sẽ tiếp tục bị chiếm dụng. - Khi một đối tượng hoặc biến không còn cần thiết, đặt tham chiếu của nó về
null
sẽ giúp bộ gom rác phát hiện và giải phóng bộ nhớ.
Sử dụng sai closure trong vòng lặp
Khi tạo closure bên trong một vòng lặp, có thể xảy ra vấn đề khi tham chiếu đến cùng một biến. Ví dụ sau đây minh họa một trường hợp mà biến i
được khai báo với var
không hoạt động đúng.
1for (var i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 3, 3, 3
Đoạn mã này không đưa ra kết quả mong muốn vì i
tham chiếu đến giá trị 3 ở cuối vòng lặp. Để khắc phục, bạn nên sử dụng let
để tách phạm vi hoặc dùng hàm được gọi ngay lập tức.
1for (let i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 0, 1, 2
Bằng cách sử dụng let
, phạm vi của i
được tách biệt cho từng vòng lặp, cho ra kết quả mong đợi.
Tóm tắt
Trong TypeScript, closure có thể mang lại mã nguồn an toàn và dễ dự đoán hơn nhờ tận dụng hệ thống kiểu dữ liệu. Sử dụng closure đúng cách cho phép đóng gói dữ liệu và thiết kế linh hoạt các hàm bậc cao. Ngoài ra, cần chú ý đến việc quản lý bộ nhớ và tham chiếu phạm vi không mong muốn khi sử dụng closure.
Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.