Functions — פונקציות - חלקי קוד לשימוש חוזר

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

ידע מוקדם: הכרות בסיסית עם המחשב ועם הבסיס של HTML ו- CSS, וכן סיום במלאו של מודול צעדים ראשונים ב-JavaScript.
מטרה: הבנה של עקרונות היסוד בנושא פונקציות ב-JavaScript.

איפה נמצא פונקציות?

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

כמעט כל פעם שאנחנו עושים שימוש בביטוי מסויים של JavaScript שעושה שימוש בסוגריים רגילות () וזה לא במסגרת לולאת for או לולאות while ו- do...while loop או משפטי תנאי if...else אנחנו בעצם עושים שימוש בפונקציות.

פונקציות מובנות (Built-in) של הדפדפן

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

let myText = 'I am a string';
let newString = myText.replace('string', 'sausage');
console.log(newString);
// the replace() string function takes a source string, 
// and a target string and replaces the source string,
// with the target string, and returns the newly formed string

או בכל פעם שעשינו מניפולציה על מערך:

let myArray = ['I', 'love', 'chocolate', 'frogs'];
let madeAString = myArray.join(' ');
console.log(madeAString);
// the join() function takes an array, joins
// all the array items together into a single
// string, and returns this new string

או בכל פעם שרצינו ליצור מספר רנדומלי:

let myNumber = Math.random();
// the random() function generates a random number between
// 0 and up to but not including 1, and returns that number

אנחנו בעצם השתמשנו בפונקציות.

תשומת לבכם: נסו להכניס את השורות קוד הרשומות למעלה לקונסולה אם אתם לא זוכרים איך לעשות בהם שימוש.

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

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

functions vs. methods  - פונקציות מול מתודות

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

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

כמו כן, ראינו הרבה פונקציות מותאמות -custom functions - פונקציות שאנחנו הגדרנו בעצמנו ולא מוגדרות בתוך הדפדפן עצמו. בכל פעם שאנחנו רואים שם שאינו שם שמור של השפה ולאחריו סוגריים רגילות (), זוהי פונקציה שהמפתח הגדיר בעצמו. בתרגול שלנו random-canvas-circles.html וגם בקוד המקור שלו במאמר בנושא לולאות, כללנו פונקציה ()draw שאנחנו בנינו שנראית כך :

function draw() {
  ctx.clearRect(0,0,WIDTH,HEIGHT);
  for (let i = 0; i < 100; i++) {
    ctx.beginPath();
    ctx.fillStyle = 'rgba(255,0,0,0.5)';
    ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI);
    ctx.fill();
  }
}

פונקציה זו מציירת 100 עיגולים אקראיים בתוך אלמנט <canvas> . בכל פעם שאנחנו רוצים לעשות זאת, אנחנו יכולים להפעיל את הפונקציה עם הקוד הבא:

draw();

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

פונקציות יכולות להכיל כל קוד שנרצה - אנחנו אפילו יכולים לקרוא לפונקציות אחרות מתוך פונקציות. הפונקציה למעלה לדוגמא, קוראת לפונקציה ()random שלוש פעמים, כפי שאנו רואים בקוד הבא:

function random(number) {
  return Math.floor(Math.random()*number);
}

אנחנו צריכים לבנות את הפונקציה ()random כי הפונקציה המובנית של הדפדפן ()Math.random מייצרת לנו מספר רנדומלי, אבל מספר רנדומלי עשרוני בין 0 ל-1. אנחנו לעומת זאת רצינו מספר רנדומלי שלם ולא עשרוני ושיהיה בין 0 למספר מסויים שאנחנו נגדיר לו.

Invoking functions - קריאה לפונקציה

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

function myFunction() {
  alert('hello');
}

myFunction()
// calls the function once

Anonymous functions - פונקציות אנונימיות

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

function myFunction() {
  alert('hello');
}

אבל אנחנו גם יכולים ליצור פונקציה שאין לה שם:

function() {
  alert('hello');
}

פונקציה שכזו נקראת פונקציה אנונימית - anonymous function - אין לה שם. היא גם לא עושה כלום בעצמה. בדרך כלל אנחנו נשתמש בפונקציה אנונימית ביחד עם ״מטפל אירוע״ - event handler. לדוגמא, הקוד הבא יריץ את הקוד בנמצא בתוך הפונקציה, כאשר הכפתור myButton נלחץ:

var myButton = document.querySelector('button');

myButton.onclick = function() {
  alert('hello');
}

הדוגמא למעלה דורשת שיהיה אלמנט <button> זמין על גבי הדף כדי שנוכל לבחור אותו וללחוץ עליו. ראיתם מבנה שכזה מספר פעמים במהלך הקורס ואתם תלמדו עליו עוד בהמשך הקורס.

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

var myGreeting = function() {
  alert('hello');
}

ניתן להפעיל את הפונקציה הזו (invoke) באמצעות:

myGreeting();

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

let anotherGreeting = myGreeting;

הפונקציה הזו יכולה להיות מופעלת בשתי הדרכים:

myGreeting();
anotherGreeting();

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

function myGreeting() {
  alert('hello');
}

אנחנו נשתמש בפונקציות אנונימיות בעיקר על מנת להריץ קוד בתגובה לאירוע שהתרחש - כמו לחיצה על כפתור - וזאת באמצעות ״מטפל אירוע״ - event handler. לדוגמא:

myButton.onclick = function() {
  alert('hello');
  // I can put as much code
  // inside here as I want
}

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

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

לתשומת לב: פרמטרים אלו נקראים ארגומנטים

כדוגמא, הפונקציה המובנית של הדפדפן לא דורשת שום פרמטרים. כשאנחנו קוראים לה, היא מחזירה מספר עשרוני בין 0 ל-1. ()Math.random:

let myNumber = Math.random();

הפונקציה המובנית של הדפדפן ()replace לעומת זאת, צריכה שני ארגומנטים - מחרוזת משנה שנרצה לחפש בתוך מחרוזת ראשית ואת המחרוזת המשנה שתחליף את זו שמצאנו בתוך המחרוזת הראשית:

let myText = 'I am a string';
let newString = myText.replace('string', 'sausage');

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

חשוב לדעת כי לפעמים ארגומנטים יהיו אופציונליים - כלומר אנחנו לא נהיה חייבים לציין אותם. אם לא נציין אותם כאשר הם אופציונליים, הפונקציה בדרך כלל תאמץ סוג של התנהגות ברירת מחדל. לדוגמא, הפרמטר של פונקציה ()join של מערכים הוא אופציונלי:

let myArray = ['I', 'love', 'chocolate', 'frogs'];
let madeAString = myArray.join(' ');
// returns 'I love chocolate frogs'
let madeAString = myArray.join();
// returns 'I,love,chocolate,frogs'

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

סקופ של הפונקציה והתנגשויות

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

הסקופ או התחום שנמצא מחוץ לכל הפונקציות שלנו נקרא global scope. ערכים שמוגדרים ב-global scope נגישים מכל מקום בקוד.

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

לדוגמא, נניח ויש לנו קובץ HTML אשר קורא לשני קבצי JavaScript חיצוניים, ובשניהם יש משתנה ופונקציה מוגדרים שמשתמשים באותו שם:

<!-- Excerpt from my HTML -->
<script src="first.js"></script>
<script src="second.js"></script>
<script>
  greeting();
</script>
// first.js
var name = 'Chris';
function greeting() {
  alert('Hello ' + name + ': welcome to our company.');
}
// second.js
var name = 'Zaptec';
function greeting() {
  alert('Our company is called ' + name + '.');
}

שתי הפונקציות שאנחנו רוצים לקרוא להם נקראות ()greeting, אבל אנחנו יכולים לגשת רק לפונקציה ()greeting שבקובץ second.js - שכן הקישור לקובץ מבוצע ב -HTML מאוחר יותר בקוד שלנו, ולכן המשתנים והפונקציה שלו דורסים את אלו שב- first.js.

לתשומת לב: ניתן לראות את הדוגמא ב-GitHub וכן את קוד המקור).

שמירה על הקוד שלנו נעול בתוך סקופ מונע בעיות שכאלו ונחשב כ- best practice.

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

למידה אקטיבית: לשחק עם ה-scope

נסתכל על דוגמא על מנת להבין מהו scope.

  1. ראשית, צרו לעצמכם עותק של הדוגמא שלנו function-scope.html. הקובץ מכיל 2 פונקציות שנקראות ()a ו- ()b וכן שלושה משתנים - x, y, ו- z - שניים מתוכם מוגדרים בתוך פונקציות ואחד מתוכם מוגדר ב-global scope. בנוסף, הדוגמא גם מכילה פונקציה שלישית שנקראת ()output, אשר מקבלת ארגומנט אחד ומציגה אותו בתוך פסקה על גבי הדף.
  2. פתחו את הדוגמא בדפדפן ובעורך הקוד שלכם.
  3. הקלידו בקונסולה את הקוד הבא:
    output(x);
    אתם אמורים לראות שהערך של המשתנה x הוצג למסך.
  4. כעת נסו להזין את הקוד הבא לקונסולה:
    output(y);
    output(z);
    שתי הפקודות הללו יחזירו לנו שגיאה ביחד עם המשפט: "ReferenceError: y is not defined". מדוע? הסיבה נעוצה ב-scope של הפונקציות: - y ו- z נעולים בתוך הפונקציות ()a ו- ()b ולכן הפונקציה ()output לא יכולה לגשת אליהם כשהיא נקראת מה-global scope.
  5. עם זאת, מה לדעתכם יקרה כשנקרא לפונקציה ()output מתוך הפונקציות? נסו לשנות את ()a ו- ()b שייראו כך:
    function a() {
      let y = 2;
      output(y);
    }
    
    function b() {
      let z = 3;
      output(z);
    }
    שמרו את הקוד ואז העלו מחדש את הדף בדפדפן ונסו לקרוא לפונקציות ()a ו- ()b מהקונסולה:
    a();
    b();
    אתם אמורים לראות את הערכים של y ו- z על גבי הדף. זה עובד מכיוון שהפונקציה ()output מופעלת מתוך פונקציה אחרת - כלומר מתוך אותו סקופ שבו מוגדרים המשתנים שהיא מדפיסה. הפונקציה ()output עצמה זמיני מכל מקום, שכן היא מוגדרת ב-global scope.
  6. נסו עכשיו לעדכן את הקוד שלכם כך:
    function a() {
      var y = 2;
      output(x);
    }
    
    function b() {
      var z = 3;
      output(x);
    }
    רענון והעלו את הדף שוב והזינו את הקוד הבא בקונסולה:
  7. a();
    b();
    גם a() וגם b() מחזירים את הערך של x — 1. זה עובד מכיוון שלמרות ש-()output לא מוגדרת באותו סקופ ש- x מוגדר בו, אבל x הוא משתנה גלובלי אז הוא זמין בכל מקום בקוד.
  8. לבסוף, עדכנו את הקוד כך:
    function a() {
      var y = 2;
      output(z);
    }
    
    function b() {
      var z = 3;
      output(y);
    }
    שמור ורענן את הדף. לאחר מכן הזן את הקוד הבא בקונסולה:
  9. a();
    b();
    הפעם כשקראנו ל- ()a ו- ()b אנחנו נקבל שגיאה מסוג "ReferenceError: z is not defined" זה מכיוון שביצוע הקוד ()output והמשתנים שהם מנסים להשיג לא מוגדרים בתוך אותו סקופ של פונקציה - המשתנים בעיקרון בלתי נראים לקריאות הפונציה הזו.

לתשומת לבכם: אותו מנגנון סקופינג לא חל על לולאות { ... }(...) for ובלוקים של תנאים - { ... }(...) if הם אמנם נראים דומים, אבל הם לא אותו דבר. אל תתבלבלו.

לתשומת לבכם: השגיאה ReferenceError: "x" is not defined היא אחת מהשגיאות השכיחות שתיתקלו בה. אם קיבלתם שגיאה שכזו, וודאו שהגדרת את המשתנה הרלוונטי ובסקופ הרלוונטי.

פונקציות בתוך פונקציות

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

function myBigFunction() {
  var myValue;

  subFunction1();
  subFunction2();
  subFunction3();
}

function subFunction1() {
  console.log(myValue);
}

function subFunction2() {
  console.log(myValue);
}

function subFunction3() {
  console.log(myValue);
}

חשוב לוודא שהערכים שמבוצע בהם שימוש בתוך הפונקציה, מוגדרים בסקופ הנכון. הדוגמא למעלה תחזיר לנו שגיאה ReferenceError: myValue is not defined מכיוון שאמנם המשתנה myValue מוגדר באותו סקופ שהפונקציה נקראת, אך הוא לא מוגדר בתוך הפונקציות עצמן - בתוך הקוד שירוץ בפועל כשאנחנו קוראים לתתי פונקציות. על מנת לגרום לכך לעבוד, היינו צריכים להעביר אליהם את הפונקציה כפרמטר כמו שרשום בקוד מטה:

function myBigFunction() {
  var myValue = 1;
      
  subFunction1(myValue);
  subFunction2(myValue);
  subFunction3(myValue);
}

function subFunction1(value) {
  console.log(value);
}

function subFunction2(value) {
  console.log(value);
}

function subFunction3(value) {
  console.log(value);
}

לסיכום

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

ראו גם

במודול זה