Closure ב-TypeScript
במאמר זה נסביר מהו Closure ב-TypeScript.
YouTube Video
Closure ב-TypeScript
מהו Closure?
Closure מתייחס ליכולת לשמור הפניות להיקף (סביבה) שבו הפונקציה הוגדרה, גם כאשר קוראים לה מחוץ להיקף הזה. להלן, Closure יוסברו יחד עם הערות סוג (type annotations).
פשוטו כמשמעו, Closure משלב פונקציה יחד עם סביבת המשתנים שבה פונקציה זו הוגדרה, ומאפשר גישה לסביבה זו כאשר הפונקציה נקראת.
המנגנון הבסיסי של Closure
ב-TypeScript, כאשר פונקציה מוגדרת בתוך פונקציה אחרת, אפשר לראות כי הפונקציה הפנימית יכולה לגשת למשתנים של הפונקציה החיצונית. להלן דוגמה בסיסית ל-Closure עם הערות סוג (type annotations).
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
הוא() => void
, כלומר היא מחזירה פונקציה. - סוג
innerFunction
מוגדר במפורש כ-void
, כלומר אינה מחזירה ערך.
שימושים ויתרונות של Closure
הצפנת מידע (Data Encapsulation)
להלן דוגמה ל-Closure עם הערות סוג עבור הצפנת מידע.
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
- הפונקציה
createCounter
מחזירה פונקציה מסוג() => number
. - המשתנה
count
מוגדר מסוגnumber
ומיוצג בתוך ה-Closure.
פונקציות מסדר גבוה (Higher-Order Functions)
Closure שימושיים כאשר יוצרים פונקציות מסדר גבוה. להלן דוגמה לפונקציה מסדר גבוה עם הערות סוג ברורות.
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
מקבלת ארגומנט מסוגnumber
ומחזירה פונקציה מסוג(value: number) => number
.- הפונקציה הפנימית מקבלת גם היא את
value
מסוגnumber
ומחזירה את התוצאה כ-number
.
דוגמה למימוש Closure ב-TypeScript
ממש מונה עם מגבלת טווח כ-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"
- הפונקציה
rangeCounter
מחזירה פונקציה שמחזירה או ערך מסוגnumber
אוstring
. - בפונקציה הפנימית, אם
count
עולה עלmax
, היא מחזירה הודעה מסוגstring
; אחרת, היא מחזירה ערך מסוגnumber
.
אמצעי זהירות בשימוש ב-Closure
דליפות זיכרון אפשריות מ-Closure
Closure עשויים לשמור משתנים מהיקף חיצוני, דבר העלול לגרום לעיתים לדליפות זיכרון. יש לשחרר Closure מיותרים מהזיכרון באופן מפורש.
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
- בקטע קוד זה, המערך
largeArray
שנוצר בתוךcreateLeak
אמור להשתחרר מהזיכרון כשהוא יוצא מהיקף, אך זה לא קורה כיוון שה-Closure לוכד אתlargeArray
. כל עודleakyFunction
קיימת, הזיכרון המיותר הזה יישמר. - כאשר אינו נדרש עוד אובייקט או משתנה, הגדרת ההפניה שלו ל-
null
תאפשר למנגנון איסוף הזבל (garbage collector) לאתר ולשחרר את הזיכרון.
שימוש לא נכון ב-Closure בתוך לולאות
כאשר יוצרים Closure בתוך לולאה, עלולות להיות בעיות התייחסות לאותו משתנה. הדוגמה הבאה ממחישה מקרה שבו המשתנה i
אינו עובד כראוי.
1for (var i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 3, 3, 3
קוד זה לא נותן את התוצאה הרצויה כיוון ש-i
מתייחס לערך 3 בסוף הלולאה. על מנת לתקן זאת, השתמשו ב-let
כדי להפריד את ההיקף לכל איטרציה, או השתמשו בפונקציה שמופעלת מיידית.
1for (let i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 0, 1, 2
בעזרת שימוש ב-let
, ההיקף של i
מופרד עבור כל איטרציה בלולאה, ומביא לתוצאות הצפויות.
סיכום
ב-TypeScript, Closure עשויים להוביל לקוד בטוח וניתן לחיזוי בזכות מערכת הסוגים. שימוש נכון ב-Closure מאפשר הצפנת מידע (encapsulation) ועיצוב גמיש של פונקציות מסדר גבוה. בנוסף, יש להיזהר בניהול זיכרון ובהפניות לא מכוונות להיקפים חיצוניים בעת שימוש ב-Closure.
תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.