Closure ב-TypeScript

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 בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video