1. דף הבית
  2. קורסים אונליין
  3. קורס Javascript אונליין
  4. פונקציות מתקדמות, רקורסיה וסקופינג ב-JavaScript

פונקציות מתקדמות, רקורסיה וסקופינג ב-JavaScript

במדריך זה נלמד את השימוש בפונקציות, נסקור סוגי פונקציות שונים כגון פונקציית חץ, פונקציה אנונימית, פונקציית IIFE, רקורסיה ועוד. נכיר את המונח scoping ונראה את ההשפעה של אופן ומיקום הגדרת משתנים בקוד בג'אווה סקריפט.
פונקציות ב-JS

הגדרת פונקציות בג'אווה סקריפט

פונקציות הן בלוקים של קוד שנועדו לבצע משימה מסוימת. הם עוזרים בארגון הקוד על ידי חלוקתו למקטעים הניתנים לשימוש חוזר.

ניתן להגדיר פונקציות באמצעות הצהרות על פונקציות (הדרך המקובלת והנפוצה ביותר), ביטויי פונקציה ופונקציות חצים.

 

נדגים הצהרה על פונקציה להלן (הדרך הנפוצה ביותר להגדרת פונקציות בג'אווה סקריפט).

function greet(name) {
    return `Hello, ${name}!`;
}

console.log(greet("Alice")); // Output: Hello, Alice!

בדוגמה זו, הגדרנו פונקציה בשם greet. הפונקציה מקבלת פרמטר יחיד בשם name. הקוד של הפונקציה הוא הקוד שמוגדר בין הסוגריים המסולסלים, והוא כולל שורת קוד אחת. המילה return מסיימת את ביצוע הפונקציה ומחזירה ערך של מחרוזת שכוללת שרשור של המילה Hello עם הערך של המשתנה name שהועבר כארגומנט לפונקציה. בשורת הקוד האחרונה, אנו קוראים לפונקציה עם הפרמטר Alice, ואת הערך שמוחזר מהפונקציה Hello, Alice אנו מדפיסים בקונסול.

 

נדגים ביטויי פונקציה להלן.

const greet = function(name) {
    return `Hello, ${name}!`;
};

console.log(greet("Bob")); // Output: Hello, Bob!

בדוגמה זו, אנו מגדירים את הפונקציה ללא שם, ומשייכים אותה לקבוע greet. הפעלת הפונקציה נעשית על ידי קריאה לקבוע greet עם הפרמטרים הרלוונטיים.

 

נדגים פונקציית חץ להלן.

const greet = (name) => `Hello, ${name}!`;

console.log(greet("Charlie")); // Output: Hello, Charlie!

בדוגמה זו, הגדרנו את הפונקציה במבנה של חץ. החץ הוא כמובן הסימן שווה (=) והסימן גדול (<) צמודים זה לזה. הגדרה של פונקציה במבנה של חץ חוסכת מקום וקריאה יותר, כאשר הפונקציה קצרה.

פרמטרים וארגומנטים בג'אווה סקריפט

פרמטרים הם המשתנים הרשומים כחלק מהגדרת הפונקציה. הם פועלים כמצייני מקום עבור הערכים שיועברו לפונקציה כאשר היא תיקרא.

ארגומנטים הם הערכים בפועל המועברים לפונקציה כאשר היא נקראת. הם הנתונים המסופקים לפרמטרים של הפונקציה.

דוגמה:

function greet(name) {  // 'name' is a parameter
    console.log(`Hello, ${name}!`);
}

greet("Alice");  // 'Alice' is an argument

בדוגמה זו, הפונקציה greet מקבלת פרמטר בשם name. בשורת הקוד האחרונה, מועבר הארגומנט Alice. בעת ביצוע הפונקציה, הפרמטר name יקבל את ערך הארגומנט Alice שהועבר לפונקציה.

 

פונקציה יכולה להחזיר ערך ריק באמצעות הצהרת return ללא ערך.

דוגמה:

function checkAge(age) {
    if (age < 18) {
        console.log("You are too young to vote.");
        return; // Return with no value to exit the function early
    }
    console.log("You are old enough to vote.");
}

// Test the function
checkAge(15); // Output: "You are too young to vote."
checkAge(20); // Output: "You are old enough to vote."

בדוגמה זו, הגדרנו את הפונקציה checkAge אשר מקבלת פרמטר age ומדפיסה בקונסול הודעה בהתאם. אם מתקיים התנאי age < 18, מודפסת מחרוזת לקונסול ומבוצעת יציאה מהפונקציה.

 

פונקציה יכולה שלא לכלול פרמטרים כלל.

דוגמה:

// Function definition with no parameters
function sayHello() {
    console.log("Hello, World!");
}

// Function call
sayHello();  // Output: Hello, World!

בדוגמה זו, הפונקציה sayHello אינה מקבלת פרמטרים.

 

פונקציה יכולה לכלול יותר מפרמטר אחד.

דוגמה:

function add(a, b) {
    return a + b;
}

console.log(add(2, 3)); // Output: 5

בדוגמה זו, הפונקציה מקבלת 2 פרמטרים a ו-b ומחזירה את הסכום שלהם.

 

פונקציה יכולה לכלול פרמטרים עם ערך ברירת מחדל. פרמטרים עם ערך ברירת מחדל חייבים להיות הפרמטרים האחרונים ברשימת הפרמטרים שמקבלת הפונקציה.

דוגמה:

// Function definition with three parameters, two of which have default values
function createGreeting(greeting, name = "Guest", punctuation = "!") {
    return `${greeting}, ${name}${punctuation}`;
}

// Function calls
console.log(createGreeting("Hello")); // Output: Hello, Guest!
console.log(createGreeting("Hi", "Alice")); // Output: Hi, Alice!
console.log(createGreeting("Good morning", "Bob", ".")); // Output: Good morning, Bob.

בדוגמה זו, הפונקציה createGreeting מקבלת 3 פרמטרים. הפרמטר הראשון הוא greeting, השני הוא name עם ערך ברירת מחדל Guest והשלישי הוא punctuation עם ערך ברירת מחדל !. ניתן לראות בדוגמה 3 קריאות שונות לפונקציה createGreeting, פעם אחת עם ארגומנט יחיד, פעם אחת עם 2 ארגומנטים ופעם שלישית עם 3 ארגומנטים. בכל קריאה לפונקציה עם פחות מ-3 ארגומנטים, הארגומנטים "החסרים" (אלו שלא הועברו) יקבלו את ערך ברירת המחדל שהוגדר בפונקציה.

 

ניתן להגדיר פרמטר מסוג Rest ("השאר"), שהוא פרמטר שלפניו מופיעות 3 נקודות. פרמטר Rest מייצג את "שאר הערכים שהועברו", ובכך מאפשר להעביר כמה ארגומנטים שרוצים לפונקציה.

פרמטר Rest מתפקד כמערך של הערכים שהועברו כארגומנטים לפונקציה ולא שוייכו לאף פרמטר.

לא יכול להיות בפונקציה יותר מפרמטר Rest יחיד. אם מוגדר פרמטר Rest לפונקציה, אז הוא חייב להיות האחרון ברשימת הפרמטרים בפונקציה.

ניתן לשלב באותה פונקציה פרמטרים עם ערכי ברירת מחדל ופרמטר Rest.

דוגמה:

// Function definition with three parameters, one default value, and a rest parameter
function introducePerson(name, age = 30, ...hobbies) {
    let introduction = `Hello, my name is ${name} and I am ${age} years old.`;
    if (hobbies.length > 0) {
        introduction += ` My hobbies are: ${hobbies.join(", ")}.`;
    } else {
        introduction += " I have no hobbies.";
    }
    return introduction;
}

// Function calls
console.log(introducePerson("Alice")); 
// Output: Hello, my name is Alice and I am 30 years old. I have no hobbies.

console.log(introducePerson("Bob", 25, "reading", "swimming", "hiking")); 
// Output: Hello, my name is Bob and I am 25 years old. My hobbies are: reading, swimming, hiking.

console.log(introducePerson("Charlie", 40)); 
// Output: Hello, my name is Charlie and I am 40 years old. I have no hobbies.

בדוגמה זו, הגדרנו את הפונקציה introducePerson שמקבלת פרמטר name, פרמטר age עם ערך ברירת מחדל 30, ופרמטר hobbies שהוא פרמטר מסוג Rest ומתפקד כמערך של כל יתר הארגומנטים שהועברו לפונקציה. הדגמנו 3 קריאות לפונקציה. בקריאה הראשונה העברנו רק את הארגומנט Alice עבור הפרמטר name ואז נקבע שערך הפרמטר age יהיה 30 (ברירת המחדל) וערך הפרמטר hobbies יהיה מערך ריק (מערך בגודל אפס). בקריאה השניה העברנו את הארגומנט Bob עבור הפרמטר name, את הארגומנט 25 עבור הפרמטר age ושורה נוספת של ערכים שהוכנסו כולם למערך hobbies. בקריאה השלישית העברנו את הארגומנט Charlie עבור הפרמטר name ואת הארגומנט 40 עבור הפרמטר age, וערך הפרמטר hobbies יהיה מערך ריק.

פונקציה אנונימית בג'אווה סקריפט

פונקציה אנונימית היא פונקציה ללא שם. משתמשים בה או כארגומנט המועבר לפונקציה אחרת (שמקבלת פונקציה כפרמטר) או בקריאה ישירה לביצוע.

דוגמה:

setTimeout(function() {
    console.log("This is an anonymous function!");
}, 1000);

בדוגמה זו, קראנו לפונקציה setTimeout. זוהי פונקציה שמקבלת 2 פרמטרים: הפרמטר הראשון הוא פונקציה כלשהי והפרמטר השני הוא מספר. הפונקציה setTimeout מפעילה את הפונקציה שהועברה אליה בפרמטר הראשון לאחר מספר אלפיות שניה שנקבע בפרמטר השני. במקרה שלנו, העברנו לפונקציה setTimeout שני ארגומנטים: פונקציה שמדפיסה טקסט בקונסול ואת המספר 1000. הפונקציה שהעברנו בארגומנט הראשון תופעל לאחר 1000 אלפיות השניה, כלומר, לאחר שניה. נשים לב שלפונקציה שהעברנו כארגומנט ראשון ל-setTimeout אין שם, כיוון שאין לנו צורך להשתמש בפונקציה הזו בהמשך שוב ואין טעם להקצות לה שם.

פונקציית IIFE בג'אווה סקריפט

פונקציה שמבוצעת באופן מידי מכונה גם IIFE, ראשי תיבות של Immediately Invoked Function Expressions. היא יכולה להיות פונקציה שקבענו לה שם או פונקציה אנונימית.

כדי להפעיל פונקציה מיד, יש לעטוף אותה בסוגריים ולספק לה ארגומנטים לביצוע.

פונקציה לביצוע מידי זמינה רק פעם אחת בעת הביצוע, ולאחר מכן לא ניתן לגשת אליה.

דוגמה:

(function namedIIFE(a, b) {
    console.log(`The sum of ${a} and ${b} is ${a + b}.`);
})(3, 4);

console.log(typeof namedIIFE); // Output: undefined

בדוגמה זו, הגדרנו את הפונקציה namedIIFE שמקבלת 2 פרמטרים a ו-b ומדפיסה בקונסול את הסכום שלהם. הפעלנו את הפונקציה מיד עם ההגדרה, בכך שהפונקציה עטופה בסוגריים וסיפקנו לה את הארגומנטים 3 ו-4. לאחר סיום ביצוע הפונקציה, היא אינה זמינה עוד לשימוש, ונסיון שלנו לפנות אליה (כפי שעשינו עם typeof namedIIFE) מחזיר undefined.

 

ברוב המקרים, אין צורך לתת שם לפונקציית IIFE, מכיוון שהיא ממילא מופעלת ברגע שהיא מוגדרת ולאחר מכן לא ניתן לגשת אליה. לכן, ברוב המקרים נעדיף להגדיר פונקציית IIFE כפונקציה אנונימית.

דוגמה:

(function() {
    console.log("IIFE executed!");
})();

בדוגמה זו, הגדרנו פונקציית IIFE שהיא אנונימית, כלומר, ללא שם. הפונקציה אינה מקבלת אף פרמטר, ולכן הפעלנו אותה עם סוגריים ריקים שמסמלים שלא מועברים אליה ארגומנטים.

פונקציה שמחזירה פונקציה בג'אווה סקריפט

שימוש נפוץ וחכם בג'אווה סקיפט הוא ביצירת פונקציה שמחזירה פונקציה.

דוגמה:

function makeCounter() {
    let count = 0;
    
    return function() {
        count++;
        return count;
    };
}

const counter = makeCounter();
console.log(counter()); // Output: 1
console.log(counter()); // Output: 2

בדוגמה זו, יצרנו פונקציה בשם makeCounter שאינה מקבלת פרמטרים. בתוך הפונקציה הזו מוגדר המשתנה count עם הערך ההתחלתי 0, ומוחזרת פונקציה אנונימית שמגדילה את המשתנה count ב-1 ומחזירה את ערכו. למעשה, הפונקציה makeCounter מחזירה פונקציית-מונה. הגדרנו את הקבוע counter להיות פונקציית מונה (שמוחזרת על ידי הקריאה ל-makeCounter). הקריאה הראשונה לפונקציה counter מחזירה את הערך 1, הקריאה השניה מחזירה את הערך 2 וכן הלאה.

נשים לב שאל המשתנה count שהוגדר בפונקציה החיצונית makeCounter ניתן לגשת מהפונקציה הפנימית ש-makeCounter מחזירה. התכונה הזו מכונה סגירות (Closure).

 

ניתן ליצור פונקציה שמחזירה פונקציה למטרות נוספות, למשל ליצירת "מחולל פונקציות" גנרי.

דוגמה:

// Function that returns a function to multiply numbers
function createMultiplier(multiplier) {
    return function(number) {
        return number * multiplier;
    };
}

// Create different multiplier functions
const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);

// Use the created functions
console.log(double(5));  // Output: 10
console.log(triple(5));  // Output: 15
console.log(quadruple(5));  // Output: 20

בדוגמה זו, הגדרנו את הפונקציה createMultiplier שמקבלת את הפרמטר multiplier ומחזירה פונקציה אנונימית (ללא שם) שמקבלת פרמטר number ומחזירה את המכפלה של multiplier ב-number. קראנו לפונקציה createMultiplier שלוש פעמים: בפעם הראשונה עם הארגומנט 2 ליצירת הקבוע double שמחזיק פונקציה שמקבלת פרמטר name ומחזירה את המכפלה שלו ב-2, בפעם השניה עם הארגומנט 3 ליצירת הקבוע triple שמחזיק פונקציה שמקבלת פרמטר name ומחזירה את המכפלה שלו ב-3, ובפעם השלישית עם הארגומנט 4 ליצירת הקבוע quadruple שמחזיק פונקציה שמקבלת פרמטר name ומחזירה את המכפלה שלו ב-4. לסיום, קראנו לכל אחת מ-3 הפונקציות עם הערך 5 והדפסנו את ערכי החזרה בקונסול.

 

היתרונות של השימוש בפונקציה שמחזירה פונקציה:

  • שימוש חוזר - דפוס זה מאפשר ליצור פונקציות לשימוש חוזר שניתן להתאים אישית עם פרמטרים שונים.
  • סגירות - הפונקציה הפנימית שומרת על גישה לפרמטר של הפונקציה החיצונית גם לאחר ביצוע הפונקציה החיצונית.
  • מודולריות - על ידי החזרת פונקציה, ניתן להקיף לוגיקה ספציפית ולהשתמש בה מחדש עם ערכי קלט שונים, וליצור קוד מודולרי וניתן לתחזוקה.

פונקציה רקורסיבית בג'אווה סקריפט

פונקציה רקורסיבית היא פונקציה שקוראת לעצמה. רקורסיה היא טכניקה רבת עוצמה המשמשת לפתרון בעיות שניתן לפרק לבעיות קטנות יותר שחוזרות על עצמן.

לכל פונקציה רקורסיבית חובה לקבוע תנאי עצירה, שאם הוא מתקיים לא ממשיכים ברקורסיה, כלומר, לא ממשיכים בקריאה נוספת לאותה הפונקציה.

דוגמה:

function factorial(n) {
    if (n === 0) {
        return 1;
    }
    return n * factorial(n - 1);
}

console.log(factorial(5)); // Output: 120

בדוגמה זו, הגדרנו את הפונקציה factorial שמחשבת את הערך המתמטי עצרת. ניתן לראות כי היא קוראת לעצמה, ולכן, היא פונקציה רקורסיבית. הפונקציה מקבלת פרמטר n ובודקת את התנאי n === 0 שהוא תנאי העצירה של הפונקציה. אם התנאי מתקיים, היא מחזירה את הערך 1 ומסיימת. אם התנאי לא מתקיים, היא מחזירה מכפלה של n בתוצאה של ביצוע נוסף של הפונקציה factorial, עם הארגומנט n - 1.
בשורת הקוד האחרונה, אנו קוראים לפונקציה עם הארגומנט 5. באיטרציה הראשונה של הפונקציה, נבדק התנאי (האם 5 === 0?) וכיוון שהוא אינו מתקיים, הפונקציה מחזירה את הערך 5 כפול ערך החזרה של הפונקציה factorial עם הארגומנט 4. באופן דומה, הקריאה הפנימית של factorial עם הארגומנט 4 בודקת את התנאי (האם 4 === 0?) וכיוון שהוא אינו מתקיים, הפונקציה מחזירה את הערך 4 כפול ערך החזרה של הפונקציה factorial עם הארגומנט 3. כך ממשיכים עד שתנאי העצירה מתקיים. בסופו של דבר, יוחזר הערך 5 כפול 4 כפול 3 כפול 2 כפול 1 שהוא 120.

 

ניתן להגדיר 2 (או יותר) פונקציות שקוראות זו לזו בצורה הדדית ורקורסיבית.

דוגמה:

// Function to check if a number is even
function isEven(n) {
    if (n === 0) {
        return true; // Base case: 0 is even
    } else if (n === 1) {
        return false; // Base case: 1 is odd
    } else {
        return isOdd(n - 1); // Recursive case: call isOdd
    }
}

// Function to check if a number is odd
function isOdd(n) {
    if (n === 0) {
        return false; // Base case: 0 is even
    } else if (n === 1) {
        return true; // Base case: 1 is odd
    } else {
        return isEven(n - 1); // Recursive case: call isEven
    }
}

// Testing the functions
console.log(isEven(4)); // Output: true
console.log(isOdd(4));  // Output: false
console.log(isEven(7)); // Output: false
console.log(isOdd(7));  // Output: true

בדוגמה זו, אנו מגדירים את הפונקציות isEven המקבלת פרמטר מספרי ובודקת אם הוא זוגי, ואת הפונקציה isOdd המקבלת פרמטר מספרי ובודקת אם הוא אי זוגי. הפונקציה isEven כוללת 2 תנאי עצירה: אם הערך הוא 0 מחזירה true (כלומר, הערך זוגי) ואם הערך הוא 1 מחזירה false (כלומר, הערך אינו זוגי). בכל מקרה אחר, מחזירה את isOdd המחושב עבור מספר הקטן ב-1 (כי בדיקה האם n זוגי זהה לבדיקה האם n - 1 הוא אי זוגי). באופן דומה, הפונקציה isOdd כוללת 2 תנאי עצירה: אם הערך הוא 0 מחזירה false (כלומר, הערך אינו אי-זוגי) ואם הערך הוא 1 מחזירה true (כלומר, הערך הוא אי-זוגי). בכל מקרה אחר, מחזירה את isEven המחושב עבור מספר הקטן ב-1.

 

פונקציות רקורסיביות נותנות הרבה פעמים פתרון קל ומהיר לתכנות עבור בעיות מורכבות. עם זאת, היעילות של פונקציה רקורסיבית היא נמוכה יותר משל פונקציה רגילה, בגלל החזרתיות הרבה וגודל הזכרון הנדרש לביצוע הרקורסיה. לכן, נמליץ שלא להשתמש בפונקציות רקורסיות ככל והדבר אפשרי, במיוחד אם הפונקציה הרקורסיבית נכנסת לעומק רב, כלומר, קוראת לעצמה מספר רב של פעמים.

משתנים לוקאליים וגלובליים וסקופינג

סקופ (Scope) הוא התחום (קטע הקוד) בו זמינים משתנים וקבועים.

משתנים שהוגדרו כ-let וקבועים שהוגדרו כ-const פעילים בתוך בלוק הקוד בו הוגדרו בלבד, כלומר, בין הסוגריים המסולסלים בהם הוגדרו.

משתנים וקבועים שהוגדרו בתוך פונקציה, יהיו זמינים רק באותה הפונקציה. משתנים וקבועים שהוגדרו מחוץ לפונקציות, יקראו "גלובליים" ויהיו זמינים בסקופ הגלובלי, כלומר, לכל אורך הקוד כולו, בתוך ומחוץ לפונקציות.

דוגמה:

// Global scope variables
let globalVar = "I'm in the global scope";
const GLOBAL_CONSTANT = "I'm a global constant";

function outerFunction() {
    // Outer function scope variables
    let outerVar = "I'm in the outer function scope";
    const OUTER_CONSTANT = "I'm an outer constant";

    console.log(globalVar);        // Accessible: globalVar is in the global scope
    console.log(GLOBAL_CONSTANT);  // Accessible: GLOBAL_CONSTANT is in the global scope

    console.log(outerVar);         // Accessible: outerVar is in the outer function scope
    console.log(OUTER_CONSTANT);   // Accessible: OUTER_CONSTANT is in the outer function scope

    function innerFunction() {
        // Inner function scope variables
        let innerVar = "I'm in the inner function scope";
        const INNER_CONSTANT = "I'm an inner constant";

        console.log(globalVar);        // Accessible: globalVar is in the global scope
        console.log(GLOBAL_CONSTANT);  // Accessible: GLOBAL_CONSTANT is in the global scope

        console.log(outerVar);         // Accessible: outerVar is in the outer function scope
        console.log(OUTER_CONSTANT);   // Accessible: OUTER_CONSTANT is in the outer function scope

        console.log(innerVar);         // Accessible: innerVar is in the inner function scope
        console.log(INNER_CONSTANT);   // Accessible: INNER_CONSTANT is in the inner function scope
    }

    innerFunction();

    // console.log(innerVar);        // Not accessible: innerVar is not defined in this scope
    // console.log(INNER_CONSTANT);  // Not accessible: INNER_CONSTANT is not defined in this scope
}

outerFunction();

// console.log(outerVar);        // Not accessible: outerVar is not defined in this scope
// console.log(OUTER_CONSTANT);  // Not accessible: OUTER_CONSTANT is not defined in this scope

console.log(globalVar);        // Accessible: globalVar is in the global scope
console.log(GLOBAL_CONSTANT);  // Accessible: GLOBAL_CONSTANT is in the global scope

בדוגמה זו, אנו מדגימים הגדרה של משתנה גלובלי globalVar וקבוע גלובלי GLOBAL_CONSTANT, אשר זמינים לכל אורך הקוד. המשתנה outerVar והקבוע OUTER_CONTANT מוגדרים בפונקציה outerFunction, ולכן זמינים רק בתוכה, כולל בפונקציה הפנימית שבה innerFunction. המשתנה innerVar והקבוע INNER_CONTANT מוגדרים בפונקציה הפנימית innerVar, ולכן זמינים רק בה.

 

מספר טיפים לשימוש נכון בסקופינג:

  • הימנע ממשתנים גלובליים - צמצם את השימוש במשתנים ובקבועים גלובליים, כדי למנוע תופעות לוואי לא מכוונות והתנגשות שמות אפשרית.
  • העדף קבועים - השתמש ב-const עבור ערכים שאסור להקצות מחדש, במטרה למנוע שינויים מקריים.
  • צמצם סקופ - הגבל את היקף המשתנים למקום שבו הם נחוצים, כדי לשפר את קריאת הקוד ותחזוקתו, להקטין את הסיכוי לבאגים ולחסוך בזכרון.
הוספת תגובה
אנו משתמשים בעוגיות על מנת לשפר את חווית המשתמש באתר. מדיניות הפרטיותאני מסכים