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"
- Kiểu trả về của
outerFunction
là() => void
, cho biết rằng nó trả về một hàm. - Kiểu của
innerFunction
được đặt rõ ràng làvoid
, chỉ ra rằng nó không có giá trị trả về.
Ứ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 có kiểu() => number
. - 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. 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
nhận một đối số kiểunumber
và trả về một hàm kiểu(value: number) => number
.- Hàm bên trong cũng chấp nhận
value
kiểunumber
và trả về kết quả kiểunumber
.
Ví dụ về cách cài đặt closure trong TypeScript
Cài đặt một bộ đếm giới hạn phạm vi dưới dạng closure với chú thích kiểu dữ liệu.
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ụ dưới đây minh họa trường hợp biến i
không hoạt động như mong muốn.
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.