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מחזירה את הפונקציה הפנימיתinnerFunction.innerFunctionמציגה את הערך של המשתנהouterVariableמהפונקציה החיצונית.
שימושים ויתרונות של 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היא פונקציית מסדר גבוה שיוצרת פונקציה שמכפילה במספר שהיא מקבלת כארגומנט.- הפונקציה הפנימית למכפלה גם מקבלת מספר ומחזירה את תוצאת הכפל כמספר.
דוגמה למימוש 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 שמוצהר עם var לא מתנהג כראוי.
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 בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.