מחלקות ב javaScript

תלמיד: מאסטר, כיצד כותבים מחלקה Class בג׳אווהסקריפט?
מאסטר: ג׳אווהסקריפט היא דינמית, כחומר ביד היוצר – עליך להגדיר בעצמך כיצד נראה Class.
תלמיד: בעצמי? אין איזו המלצה מקובלת? Best Practice? Pattern?
מאסטר: יש הרבה. מאסטר רזיג אוהב את אופצייה #1, מאסטר כץ אוהב את אופציה #3. אם יש לך מערכת שדורשת אלף Classes, בג׳אווהסקריפט אתה יכול להגדיר כל Class במערכת בצורה ייחודית.
תלמיד: כל Class בצורה ייחודית?? כיצד ניתן לתחזק קוד שכזה? כיצד אפשר בכלל לקרוא אותו?
מאסטר: אל תהיה טיפש. זה שהשתמשתי באמירה כלשהי לא אומר שאתה באמת צריך לרוץ לעשות את זה.
תלמיד: הא…שייך לסדרה מבוא מואץ ל JavaScript ו jQuery

מוטיבציה
בג׳אווהסקריפט אין מחלקות (Classes בג'אווה / NET.), אולם רוב המפתחים המקצועיים בג׳אווהסקריפט מדמים מחלקות. לא ניתן להאשי אותם: מחלקות הן דבר שימושי.

בעוד שמפתח הג׳אווהסקריפט, הבודד, המקצוען, נהנה מהחופש לעצב את צורת המחלקה ע״פ הצורך הקונקרטי באותה הנקודה, אנו – המפתחים החדשים המגיעים לג׳אווהסקריפט, מעדיפים קצת יותר סדר ויציבות.

בעצם, אולי זו לא רק שאיפה של "מפתחים חדשים". ניתן לראות שהקהילות של שפות Ruby וג׳אווהסקריפט מתבגרות בשנים האחרונות ונותנות דגש רב יותר לרעיונות כמו הכמסה (Encapsulation), ניהול תלויות, מודולריות וממשקים – כל אותם דברים שמפתחי ג׳אווה או דוטנט קלאסיים עסקו בהם מאז ומעולם (ניתן לומר ״לכאורה״, ניתן לומר ״קצת בהגזמה״).

ישנן עשרות צורות אפשריות לתאר מחלקות בג׳אווהסקריפט. ניתן לחלק אותן בבירור ל-2 משפחות:

  • מחלקות בעלות אכיפה נוקשה של הכמסה (מבוססות closure).
  • מחלקות יעילות מבחינת צריכת זיכרון ואפשרויות הורשה (מבוססות prototype).

שילוב אלגנטי שנותן ״גם וגם״ – עדיין לא ראיתי.

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

הנה המחלקה בשפת Java אותה ארצה לדמות בג'אווהסקריפט:

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

משפחה א׳ – אכיפה נוקשה של הכמסה.

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

תיאור זה של מחלקה הוא ידוע מאוד ונקרא ״revealing module״, המבנה הבסיסי אמור להיות מוכר – עסקנו בו בפוסט הקודם בסדרה. זו גם הצורה בה פרויקט jQuery ממליץ לכתוב מחלקות.
יצירת instance חדש נעשה ע"י קריאה ל Factory Function (המקבילה הג'אווהסקריפטית ל Factory Method). בכדי "לסמן" את ה Factory Function – אני ממליץ לקרוא לה createXXX.

הערה: ה "expect" שאתם רואים בסוף הקוד היא וריפיקציה של בדיקת-יחידה, בכדי לדעת שהקוד עובד כפי שאנו מצפים ממנו. בפעול המשמעות היא שהביטוי בתוך ה expect הוא שווה לביטוי בתוך ה toBe.

הנה צורה אחרת, פחות נפוצה. כל member שמוגדר עם "this" הוא ציבורי וכל member שמוגדר עם var הוא פרטי.
אני אישית מחבב אותה יותר: הקוד קצר יותר. הניראות (visibility) מצוינת באותו המקום בו מוגדר ה member/method כך שבמבט חטוף אני יכול לדעת מה private ומה לא (כמו בג׳אווה – המלים השמורות private ו public). אני לא צריך לגלול לתחתית המחלקה על מנת לדעת מהי הניראות, כמו בדוגמה הקודמת
.
בעת יצירת instance, יש לזכור להשתמש במילה השמורה new. הקונבנציה המקובלת היא לקרוא לבנאים באות ראשונה גדולה (Capital Letter). זהו חיסרון מסוים יחסית לצורה הקודמת, מכיוון שיש להיות מודעים לצורה בה המילה השמורה this עובדת בג'אווהסקריפט. פוסט המשך בסדרה מסביר את הנושא לעומק.

סכנה מוחשית במבנה זה הוא החלפה של הנראות private public. מכיוון שגם הגישה למתודה/משתנה היא שונה ע"פ הנראות שלו (this.xx או xx) אזי סביר שבעת שינוי הנראות נשכח לתקן חלק מהקריאות לצורה המעודכנת – ונשבור את הקוד. אני ממליץ להשתמש בצורה זו תחת הכלל: כל המשתנים – פרטיים, כל הפונקציות – ציבוריות. מבנה כזה שימושי עבור Data Objects, Mock Objects וכו'.

משפחה ב׳ – יעילות בצריכת הזיכרון ואפשרויות הורשה
משפחה זו מתבססת על prototype שהוא האמצעי בג׳אווהסקריפט לשתף קוד בין אובייקטים שונים. Prototype הוא גם הכלי לבטא הורשה בין אובייקטים (או ״מחלקות״). אם אתם רוצים להשתמש בהורשה (לא, לא!) אזי זו הדרך ללכת בה.

תיאור זה של מחלקה הוא נפוץ מאוד, הוא קרוי לעתים Prototype Pattern. אתם אמורים לזהות את המבנה הבסיסי מהפוסט הקודם בסדרה. בעצם זו וריאציה קלה על ה "Pattern הרשמי" מכיוון שהיא כוללת "סימון" של private members – בעזרת התחילית "_" (קו תחתון).  סימון זה הוא קונבנציה בלבד – ועל המפתחים לשמור על משמעת אישית על מנת לא-להשתמש ב private members מחוץ למחלקה.

תיאור זה הוא הדרך שבה CoffeeScript בחרה לתאר מחלקות. הוא מאגד את כל המחלקה בבלוק אחד (נדיר למשפחה ב') ונפטר מה object literal notation להגדרת הפונקציות.
החיסרון? המבנה מורכב מעט יותר: יש שימוש גם ב closure וגם ב Constructor.

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

תיאור זה הוא בעצם Singleton. התחביר שלו פחות אלגנטי, אבל יותר גרוע – אין פה אפשרות ל instantiatiation (לייצר instances). עבור בדיקות או מודולריזציה זו מכה רצינית: מחלקות אלו מופעלות תמיד בעת הטעינה והעותק היחיד שלהן משותף לכולם.

הערות? מחשבות? – אשמח לשמוע!

שיהיה בהצלחה!

JavaScript's This – למפתחי ג'אווה ו #C מנוסים

"מפתח JavaScript הופך ממתכנת ממוצע למתכנת מקצועי ביום שהוא מבין את המילה השמורה this".כמפתחי ג'אווה או #C אתם אמורים להישאר כרגע פעורי פה: האם ייתכן ש"מתכנתי ג'אווהסקריפט" לא מבינים דבר בסיסי כמו המילה השמורה this?!
אבל בג'אווהסקריפט, כמו בג'אווהסקריפט – יש הפתעות במקומות הלא צפויים, ו this איננה יוצאת דופן.

חלק גדול ממפתחי הג'אווהסקריפט הם מפתחי צד שרת (Java, Ruby, #C) אשר משתמשים בה "לכתוב קצת UI", סוג של "מפתחים מזדמנים".
אתם לא באמת צריכים להבין את כל הפינות של ג'אווהסקריפט על מנת לפתח בווב. עבור רוב עבודות הג'אווהסקריפט, תוכלו להסתדר עם "להבין מספיק".
אם אתם עושים עושים בג'אווהסקריפט קצת מעבר – להבין את this בהחלט יכול לעזור.

שייך לסדרה מבוא מואץ ל JavaScript ו jQuery

תשכחו את מה שידעתם בג'אווה / #C…

Scope בג'אווה כפי שאתם מכירים אותו. פשוט וברור.

בג'אווה, להבין את ה scope זה דיי פשוט: scope נוצר ע"י סוגריים מסולסלים.
אם אני רוצה לדעת מיהו המשתנה n – פשוט אטייל בעין, scope אחר scope עד שאמצא היכן הוא הוגדר.
אם אני רוצה לדעת מיהו this – פשוט אטייל בעין, scope אחר scope עד למחלקה הראשונה שאני רואה. כמעט תמיד תהיה רק מחלקה יחידה (קינון מחלקות הוא דיי נדיר בג'אווה).

בג'אווהסקריפט פיצלו את ה scope, כפי שקיים בג'אווה, לשניים:

  • Scope להגדרת משתנים, המוגדר ע"י פונקציה (ולא סוגריים מסולסלים! [א])
  • Context[ב] להגדרת this, המוגדר ע"י שייכות קונקרטית לאובייקט – בזמן ריצה. הסבר בהמשך.
הכללים לקביעת ה-context הם שונים מהכללים לקביעת ה scope. עצם ההבנה שהחוקים למציאת ה scope הם לא דומים לחוקים למציאת ה context – היא מחצית הדרך להבנה של this.

כיצד נוצר Context?

כלל #1: כאשר פונקציה (function) שאינה משויכת ישירות לאובייקט נקראתה context יהיה האובייקט הגלובלי. כלומר this יצביע לאובייקט הגלובלי. האובייקט הגלובלי הוא האובייקט עליו נרשמו כל המשתנים / פונקציות שהוגדרו ללא var או ללא שייכות לאובייקט כלשהו. בדפדפנים, האובייקט window (שמתאר Tab של דפדפן) נבחר לייצג את האובייקט הגלובלי. כל השמה "גלובלית" תתווסף עליו.

כלל #2: כאשר פונקציה המשויכת ישירות לאובייקט (מתודה method) נקראת – ה context שלה יהיה האובייקט אליו היא שויכה. כלומר, this יצביע לאובייקט שעליה היא הוגדרה, או ששויכה במהלך הריצה (דוגמאות בהמשך).

זהו זמן טוב לדוגמה.

השמשתי בצילומי מסך מה IDE האהוב עלי לכתיבת JavaScript, הרי הוא WebStorm. העטיפה "it" ומשפטי ה expect נובעים מכך שזו בדיקת-יחידה, הנעזרים בספרייה בשם Jasmine.
כל הבדיקות בפוסט זה עוברות, ולכם אתם יכולים להניח שהחלק שבתוך ה expect שווה לערך בתוך ה toBe.

ב1 אנחנו רואים שהפונקציה מחזירה את השם של האובייקט הגלובלי. בגלל שאני מריץ את הקוד בתוך "מריץ הבדיקות" השם שם האובייקט הגלובלי במקרה זה הוא "runner".

ב2 הגדרנו אותה פונקציה על אובייקט – והנה this התייחס לאובייקט עליה הוגדרה – הרי הוא A.

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

המילה השמורה new, ומה שהיא עושה ל this…
בנאי (constructor) בשפת ג'אווהסקריפט היא פונקציה רגילה לכל דבר. הקונבנציה המקובלת היא לקרוא לבנאי בשם עם אות ראשונה גדולה (capital letter). שאר הקסם קורא בעת הפעלת הפונקציה עם המילה השמורה new.

שימוש במילה השמורה new גורם לשלושה דברים:

  • נוצר אובייקט חדש ריק – השקול לכתיבת "{ }".
  • האובייקט החדש מועבר לבנאי, כפרמטר בשם this. הדברים האלו קורים מאחורי הקלעים – בקוד עצמו לא יהיה זכר ל this בחתימת הפונקציה.
  • במידה והבנאי לא סיפק ערך החזרה return (עדיף שלא יספק) – ג'אווהסקריפט מוסיפה return this ביציאה מהפונקציה.

הנה דוגמה:

במקרה זה, השתמשנו ב new על מנת לייצר אובייקט חדש.
למרות שהפונקציה ConstructorA (=בנאי) לא מוגדרת בתוך אובייקט, בזמן ההרצה this דווקא כן מתייחס לאובייקט.
מסקנה: אין לחפש את האובייקט במבנה הקוד, אלא לחפש את נקודת הקריאה (invocation) של הפונקציה ומאיזה context היא התבצעה.

הנה דוגמה בעייתית:

הבעיות הקשות לרוב לא צצות בפונקציות בנות 5 שורות – הן קורות באובייקטים בני עשרות או מאות שורות. הנה סימולציה לבעיה שיכולה להתרחש.
שימו לב למקרא הצבעים (יכול לעזור לעקוב):

  • משתנה / פונקציה מקומית = טורקיז.
  • משתנה / פונקציה של אובייקט = חום-צהוב.
  • (משתנה / פונקציה גלובליים = סגול)

ב1 קורה בדיוק מה שאנחנו מצפים – אנחנו מקבלים את A. זה שכפול של הדוגמה הקודמת.
ב2 הדברים משתבשים. אפשר בטעות לחשוב, שכל שימוש ב this בתוך הבנאי יתייחס לאובייקט החדש. זה לא המצב. במקרה זה הגענו לאובייקט הגלובלי.

הבעיה נובעת מכך שהפונקציה sayMyName איננה משויכת לאובייקט שנוצר – כי אם "נתלית" על ה scope של הפונקציה ConstructorA. הנה שמה צבוע בטורקיז = משתנה לוקאלי. אם אתם זוכרים את כלל #1 – פונקציה שלא קושרה לאובייקט משויכת ל context של האובייקט הגלובלי.
בגלל שמדובר ב closure [ג] היא תישאר "בחיים" ויהיה ניתן להשתמש בה גם לאחר שהפונקציה ConstructorA סיימה לרוץ.

Webstorm מזהה את הבעיה ואכן מציג אזהרה על ה this (מת עליו). כלי ניתוח ססטי כמו JSLint/JSHint – לא מזהים את המצב הזה. כנ"ל IDEs רבים אחרים.

למי שרוצה לחפור קצת יותר: ניתן לומר ש sayMyName דומה מאוד למתודה סטטית (static) בג'אווה: היא קיימת על המחלקה ולא מכירה את האובייקט – ולכן לא ניתן לגשת ממנה אל האובייקט. בעוד בג'אווה this בתוך מתודה סטטית היה מצביע על המחלקה – בג'אווהסקריפט הוא "הולך לאיבוד" ומצביע על האובייקט הגלובלי (בשל כלל #1).

הנה הפתרון המקובל לבעיה זו:

צרו משתנה ("that") ב closure שיצביע על ה context הרצוי – ואז השתמשו בו.

השילוב של ג'אווהסקריפט ו this יכול לבלבל… 

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

הקריאה ל ()objectD.objectF.getThatName זורקת שגיאה (error), בעוד רצינו שתחזיר "E".

מה שקצת מבלבל הוא ההנחה שהקריאה this.objectE תגיע ל objectE – שהוא אובייקט אח ולא אובייקט בן.
ג'אווהסקריפט מצידה לא מודיע על בעיה: השימוש ב this.objectE מוסיף ל objectF משתנה חדש בשם objectE, שערכו undefined, ומיד אחר כך צועק על כך של undefined אין מתודה בשם getMyName. בעסה.

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

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

    getThatName: function(){ return objectD.objectE.getName(); }

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

הנה תקלה נפוצה אחרת:

מה יותר טבעי למפתח ג'אווה מנוסה, מאשר לבצע introduce variable לצורך שיפור קריאות הקוד?
זהו, אני לא רוצה שתכנסו לפחד ותאמרו לעצמכם: "עם ג'אווהסקריפט לא מתעסקים. עדיף שהכל יהיה גלובלי ובלי שום 'refactoring'". בואו נבין מה קרה פה.

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

ספריית jQuery מציע utility function שיכולה לעזור, proxy.$ (לחצו על התמונה על מנת להגדיל):

proxy.$ מקבלת פונקציה ואובייקט (= context) ויוצרת פונקציה חדשה הקושרת "לעד" את ההרצה של הפונקציה ל context שנבחר.

אם אתם עובדים עם דפדפנים חדשים בלבד (+IE9), אתם יכולים להשתמש בפקודת bind של שפת ג'אווהסקריפט – שעושה בדיוק את אותו הדבר. proxy$ / bind שימושית במיוחד לצורך רישום אירועים:

השוו דוגמה זו לדוגמה הקודמת – הרעיון דומה.
ב1, לחיצה על הכפתור תפעיל את draw ב context של אובייקט ה button$[ד] – שאין לו מתודה בשם draw = תקלת זמן ריצה.
ב2, לחיצה על הכפתור תפעיל את draw ב context של האובייקט objectA – היכן שהמתודה אכן נמצאת.

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

כפי שאתם רואים call מאפשר להריץ מתודה (במקרה זה – של אובייקט A) על אובייקט (במקרה זה – D), כלומר – כך ש this יתייחס לאובייקט D.
כמובן שפונקציה לעתים דורשת ארגומנטים, call מאפשרת להוסיף אותם בסדר הנכון אחרי ה context שהועבר.
ההבדל בין call ו apply הוא רק בדרך בה מעבירים ארגומנטים לפונקציה – רשימת ארגומנטים או מערך. לא משהו גדול.

בפועל השימוש ב call יכול להיות מהיר עד פי כפליים מ apply – שוני שמשמעותי רק כאשר אתם עושים מאות או אלפי הפעלות של הפונקציה.

שיהיה בהצלחה!

——

[א] אתם יכולים לקרוא עוד קצת על הנושא בפוסט מבוא מואץ ל JavaScript עבור מפתחי Java / #C מנוסים – חלק 1, בערך באמצע.

[ב] רשמית נקרא Function Context, אבל יותר מדויק יהיה לקרוא לו Invocation Context.

[ג] ניתן לקרוא על Closure בסוף הפוסט מבוא מואץ ל JavaScript עבור מפתחי Java / #C מנוסים – חלק 1.

[ד] הכוונה ל wrapper של jQuery שעוטף את אלמנט הכפתור, אותו יצרנו בעזרת השאילתה ('.button')$.

ויזואליזציה בצד הדפדפן

.lior_code { background-color: #ebf0f2; border: 1px solid black; margin: 6px 0; padding: 4px; text-align: left; font-family: \'Courier New\', Courier, monospace; } .lior_comment { background-color: #fff2cc; border: 1px solid #BFBFBF; margin: 4px 0; padding: 4px; }

אני זוכר קבוצה של מפתחים שעבדו על מערכת בקרה לשרתים שנכתבה ב ++C. הם היו מפתחים ותיקים ומקצועיים ונראה היה שהם מסתדרים ללא בעיה.
אלו היו הימים המוקדמים של ג\'אווה / #C כטכנולוגיות צד שרת. אני זוכר את הדיון שהתרחש: הציעו לאנשי הצוות לעבור להשתמש ב #C – אבל הם לא האמינו שזה יועיל להם. \"מה יש ב #C שאין ב ++C? ניקוי זיכרון אוטומטי? אנחנו עובדים עם AutoPointers ואין לנו בעיות. #C היא טכנולוגיה למפתחים מתחילים\".
– \"יש לנו את ACE (ספריית תקשורת), אין שום דבר דומה ב #C\" (ספריית התקשורת הבסיסית של #C הייתה טובה מספיק)
– \"#C קיימת רק 3 שנים, ניתן לה כמה שנים להתבגר\" (הממ… יש בזה משהו…)
– \"אם נצטרך יום אחד לעשות משהו מיוחד – ++C תאפשר הכל\" (אין מניעה להשתמש ב ++C בתוך תוכנת #C)
– \"אין לנו זמן ללמוד שפה חדשה, אנחנו עמוסים בעבודה\"
וכו\'

אחרי שנתיים בערך כתבו את מערכת הבקרה מחדש ב #C – וזו הייתה הצלחה גדולה. זו הייתה מערכת לדוגמה שבה שימוש ב ++C היה מיותר.
האם מישהו היה שוקל כיום לכתוב מערכת שכזו ++C? – כנראה שלא.

תהליך דומה מתרחש כיום בעולם ה Client-Side. אחד האתגרים העומדים בעת פיתוח אפליקציות HTML5, ומובייל בפרט, הוא ליצור ויזואליזציה מושכת: משהו נעים למראה, ו\"מגניב\". הכלים המוכרים הם כנראה CSS3 וג\'אווהסקריפט. CSS3 הוא כלי רב-עצמה ומי שמתמחה בו יכול להגיע לתוצאות מרשימות.

ויזואליזציה של טופס, שכאשר מסיימים למלא אותו הוא מחליק לתוך מעטפה. ללא JavaScript! מקור

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

כתיבת ויזואליזציה ברמה גבוהה בעזרת CSS3 הוא תרגיל מאתגר המלמד הרבה על CSS3 ו HTML – אבל הוא קשה משמעותית יותר לתחזוקה מאשר שימוש בספרייה שהיא יותר \"גבוהה\". כנ\"ל לגבי אלמנט ה cavnas של HTML5: בעזרת קוד JavaScript ניתן לצייר על ה canvas  ברמת הפיקסל[א], וכך ליצור כל סוג של ויזואליזציה, אבל קל להגיע מהר מאוד לעשרות או אפילו מאות של קוד ג\'אווהסקריפט לא-קריא.

Browser-Side Visualization Technology Stack
ברצוני להציג בפניכם את ה \"Stack\" טכנולוגיות הוויזואליזציה של צד-הדפדפן. זו לא הרכב אפשרי יחידי, כמובן, אך זה הרכב של כלים מוצלחים ומשלימים שיכול לתמוך במשימות ויזואליזציה מורכבות. לא מדובר ב\"טוב\" או \"רע\", כי אם בבחירת הכלי הנכון למשימה.

שתי הטכנולוגיות העיקריות לוויזואליזציה ב HTML5 הן SVG ו Canvas ומעל כל אחת יש ספריות שמרחיבות ומעשירות אותה. CSS משמש בעיקר ל Styling, כלומר לקחת את הקיים ולהוסיף לו טאץ\' עיצובי. כפי שראינו למעלה, ניתן ממש \"לצייר\" בעזרת CSS – אך זו אינה מטרתו העיקרית.
הערה: אני מתעלם בכוונה מ WebGL וכל הנושא של תלת-מימד. באופן אישי אני מוצא את תלת-המימד פחות חשוב מוויזואליזציה דו-מימדית ולכן פוסט זה יעסוק רק בה.

HTML
כוללת בעיקר מבנה וטקסט. יכולות הוויזואליזציה הן דיי מוגבלות:

  • ניתן להשתמש בטבלאות לעימוד. ישנה תגית

    לקו הפרדה רוחבי.

  • ניתן ליצור ציורי ASCII מרשימים (שימוש בטקסט).
  • ישנן מספר תגיות שמשפיעות על עיצוב הטקסט כמו

    או תוויות b/u/i.
ויזואליזציה מסוג זה היא מאוד ווינטג\' – ומתאימה לשימושים… מאוד מסוימים.
תקן ה HTML5 Semantics מבקש להסיר מ HTML כל אלמנט של ויזואליזציה והפיכתו ל Data Only. לדוגמה:תג  לא אמור להשפיע על הטקסט אלא רק לסמן אותו ככזה. בקובץ ה CSS יוכלו לעצב את תג ה כבולט.
בכדי לא לשבור (מיליוני) דפי HTML5 קיימים, נותרו על כנן רוב היכולות העיצוביות של דף ה HTML, אם כי ההמלצה היא להמעיט את השימוש בהן.
CSS3
ל CSS3 יש כמה יכולות ויזואליזציה מתקדמות:

Gradients & Borders
זה אולי האלמנט בעל השימוש הנפוץ ביותר בצמד HTML5+CSS3. היכולת לקחת כל אלמנט (לרוב זו תהיה תיבה פשוטה: Div או Span), לעטוף אותו במסגרת יפה ולתת לה רקע.

אפשרויות ליצור מסגרת מעוגלת בפינה השמאלית-העליונה. מקור: http://www.css3.info

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

צורות שנוצרו בעזרת משחק במסגרת של Div בעזרת CSS. מקור:  http://www.css3.info/preview/rounded-border/

הנה cheat sheet של רשימת צורות שונות והמימוש שלהם בעזרת CSS.

אם ממשיכים במשחק, מוסיפים גם רקע (gradient) והצללות, אפשר למקם כמה אלמנטים (position: relative) אחד על גבי השני ולייצר ממש \"ציור\". הנה דוגמה ליצירת הלוגו של דפדפן אופרה בשיטה זו:

אופס! לא עובד ב IE ישן. מקור:  http://v2.desandro.com/articles/opera-logo-css/

יתרון ל CSS על פני תמונות (שהשימוש בהן היה נפוץ בעבר) הוא ש CSS הוא ציור וקטורי: תיאור של ציור, שברגע ההצגה הדפדפן יכול להשתמש ברזולוציה המלאה של המכשיר לצייר אותו. אם מנסים להציג תמונה ברזולוציה גבוהה יותר ממה שנוצרה – ייווצר עיוות של \"פיקסליזציה\"[א2].

CSS מספק גם:

  • יכולות טרנספורמציה – היכולת לסובב (rotate) ולמתוח אלמנטים ב DOM.
  • יכולות מעבר (transition) המאפשרת להפוך אלמנט ממצב א\' למצב ב\' – הנה דוגמה (בhover על העיפרון הוא ינוע לצד ימין). יכולות אלו מקבילות במידה רבה ל\"אפקטים\" של jQuery. ל jQuery יש היתרון של קביעת הפרמטרים בזמן הריצה (הרי את קובצי ה CSS יש לכתוב מראש) – כך שעדיין קל יותר להשתמש ב jQuery בדפים דינמיים.
  • אנימציה, יכולת שדומה ליכולות מעבר, אך היא תכונה של האלמנט ולא אירוע חד-פעמי. הנה דוגמה לציור CSS עם אנימציה.
סה\"כ CSS3 מציגה סט יפה של יכולות שיכול להספיק כדי לתת \"טאץ של ויזואליזציה\" בממשק \"מבוסס-טופס\". CSS הוא סטנדרטי ומוכר – מה שמבטיח מגוון רחב של כלים ודוגמאות באינטרנט.
אם אתם זקוקים ליכולות שמעבר לכך, אני ממליץ לכם להימנע מלהפוך ל\"CSS Ninja\" ופשוט להשתמש בכלי ויזואליזציה \"כבדים יותר\" עליהם נדבר בהמשך.

Canvas
אלמנט ה Canvas של HTML5 הוא כמו Div המכיל רשת של פיקסלים (bitmap) וניתן לצייר עליו בעזרת קוד javaScript. הנה דוגמה:

HTML:
JavaScript:
    function draw() {
      var canvas = document.getElementById(\"myCanvas\");
      if (canvas.getContext) {
        var ctx = canvas.getContext(\"2d\");
        ctx.fillStyle = \"rgb(200,0,0)\";
        ctx.fillRect (10, 10, 55, 50);
        ctx.fillStyle = \"rgba(0, 0, 200, 0.5)\";
        ctx.fillRect (30, 30, 55, 50);
      }
    }

תיצור את התוצאה הבאה:

הפעולות הזמינות על הקנבס הן פעולות ברמת הפשטה נמוכה (\"low level\") ומזכירות מאוד 2D API (כגון זה של ג\'אווה):

  • ציור צורות כגון קווים, מלבנים, עיגולים ונקודות כאשר אפשר לקבוע את צבע ועובי המסגרת (\"Stroke\") וצבע המילוי (\"fill\") – שיכול להיות גם gradient.
  • הרכבת של צורות (compositing) – ציור של צורות אחת על השנייה עם הגדרות אפשריות שונות כיצד יתנהגו הפיקסלים החופפים לשתיהן: האם הם יהיו של צורה A של צורה B, או פונקציה לוגית אחרת. ראו תרשים למטה המתאר את אפשרויות ה composting. 
  • טעינה של תמונה (קובץ PNG למשל) לתוך ה canvas בעזרת פקודת (drawImage (image,x,y
  • בכל שלב ה canvas משמש כ bitmap עליה ניתן לעשות פעולות, כגון טרנספורמציות – סיבוב, שינוי קנה המידה או שמירה כקובץ jpg / png.
  • אנימציה. ה canvas עצמו לא מספק יכולות אנימציה מפורשות, אולם בעזרת setInterval והרכבה (compositing) של שכבות canvas ניתן ליצור אנימציות מרשימות. SetInterval (סטנדרטי של JS) משמש על מנת לקרוא לפונקציית javaScript שתעדכן את ה Canvas כל פרק זמן. דרך אחת מקובלת היא לחזור על אותה פונקציית ציור על פעם עם ערכים אחרים (הזחה, סיבוב, צבעים שונים…). על מנת לא לצייר את כל המשטח מחדש בכל פעם ניתן להחזיק משטח \"רקע\" ומשטח \"חזית\", לבצע את השינויים במשטח החזית ולהרכיב אותו עם משטח הרקע.
    ל Canvas יש יכולת של שמירת מצב (פונקציות save ו restore) שמאפשרות לחזור לחזור למצב קודם של ה canvas. יכולת זו גם משמשת לעתים בבניית אנימציות.
    הנה דוגמה לאנימציית canvas טיפוסית. הנה הקוד הרלוונטי.
אפשרויות ההרכבה ב canvas בין source ל destination

בקישור זה תוכלו למצוא ריכוז (\"Cheat Sheet\") של היכולות העיקריות של ה Canvas.

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

(SVG (Scalable Vector Graphics
SVG איננה טכנולוגיה חדשה. היא קיימת כעשור, החל מ 1999, אבל במשך שנים היא זכתה לאימוץ חלקי – בעיקר מצד דפדפני IE שהיו חלק משמעותי מאוד מהשוק.
התקן שאנו מדברים עליו עכשיו הוא תקן SVG גרסה 2.0 – תקן שעדיין בכתיבה אבל מכיל כמה תוספות משמעותיות מאז הגרסה הקודמת SVG 1.1. העבודה על גרסה 1.2 הופסקה באמצע עבור גרסה 2.0 אך יש באמצע גרסה בשם 1.2 Tiny – אם יצא לכם להיתקל בה.
תקן SVG 2.0 כולל אינטגרציה ל CSS3 ו HTML5 ורשימה של תוספות ושיפורים שונים ומשונים. החלק החשוב בו הוא שהתקן זוכה לתמיכה רחבה מכל הדפדפנים המודרניים (+IE9) התומכים ביכולות הבסיס (שמקורה עוד ב SVG 1.1 + כמה שיפורים) ובהדרגה גם ביכולות מתקדמות יותר של 2.0 (אפקטים, פילטרים, פונטים של SVG ועוד).

SVG עצמו הוא קובץ XML שמתאר ציור וקטורי. בקובץ ה SVG ניתן להשתמש כקובץ חיצוני ל HTML (כגון תמונה מפורמט png) או כתגית SVG שמקוננת בתוך ה markup. הנה דוגמה לתגית SVG מקוננת:

<svg version=\"1.1\" baseProfile=\"full\" xmlns=\"http://www.w3.org/2000/svg\">
  <rect width=\"100%\" height=\"100%\" fill=\"red\" />
  <circle cx=\"150\" cy=\"100\" r=\"80\" fill=\"green\" />
  <text x=\"150\" y=\"125\" font-size=\"60\" text-anchor=\"middle\" fill=\"white\">SVG</text>
</svg>

הנה התוצאה:

כתיבת SVG ידנית יכולה להיות מתישה.
חלק מהיתרון של SVG היא היכולת לצייר את \"קוד\" ה SVG בעורך חיצוני כגון SVG-Edit או Google Docs Drawing – עורכים חינמיים ואונליין הנחמדים למשימות פשוטות, ועד כלים מקצועיים כגון Inskape (חינמי) או Adobe Illustrator ($$) שיכולים לייצא קובצי SVG. אם אתם רוצים ליצור ויזואליזציה וקטורית לאתר שלכם (כגון פאנל מהודר או מעטפה שטופס יחליק לתוכה) – מה יותר פשוט מלבקש מהמעצב הגרפי שיעבוד בכלי שהוא רגיל אליו ורק ישלח לכם קובץ SVG?

מלבד יכולות ציור פשוטות, SVG כולל:
  • טרנספורמציות (דומות ל CSS ו canvas שכבר ראינו)
  • יכולות הרכבה של אלמנטים / \"ציורים\" הנקראות Clipping and Masking והן עשירות מיכולות ה canvas.
  • היכולת להשפיע על אלמנטים בתוך ציור ה SVG בעזרת CSS (לדוגמה צבעי הקווים של אזור מסוים).
  • היכולת ליצור פונטים חדשים בעזרת SVG (יכולת שלא נתמכת היטב עדיין)
  • יכול להיסרק ע\"י מנועי חיפוש (SEO) ויכול לתמוך ב Accessibility.
  • ואולי היכולת המתקדמת ביותר: להשתמש ב\"פילטרים\" או \"אפקטים\" המשפיעים על אלמנטים ב SVG. הכוונה ליכולות שמזכירות תוכנות כגון Adobe Photoshop ויכולות להשיג תוצאות שלא סביר להשיג בעזרת ציור פיקסל-פיקסל (כגון canvas) או הרכבות לוגיות של אלמנטים. דוגמאות מעניינות יכולות להיות הצללה (ניתן בלחיצה לשנות את כיוון ההצללה) או הדמייה של בוקה (Bokeh) – הטשטוש הרקע שיוצרת עדשת מצלמה איכותית וצלמים רבים אובססיביים לגביו.
    למרות ההבטחה הגדולה, SVG Filters דורשים כוח חישוב רב והפילטרים המתקדמים יותר עדיין לא נתמכים ע\"י רוב הדפדפנים.

מדוע יש חפיפה בין canvas ל SVG?
קנבס הוצג לראשונה ע\"י חברת אפל בגרסאת ה Webkit ששוחררה עם MacOS X. הוא שימש לייצר Desktop Widgets של מערכת ההפעלה (המקבילה ל gadget של Windows Vista) אך היה זמין גם בדפדפן הספארי.
ביקורת רבה הופנתה אל אפל שיצרה \"תג\" חדש ולא השתמשה בתקן הסטנדרטי – הרי הוא ה SVG. בנוסף התגלה שאפל הגנה בפטנטים על התג החדש – דבר שלא היה ברור לכל כשהציגה אותו.
האם הכוונה היה לאפשר למתחרה כלשהו לאמץ את התקן ואז למצוץ את דמו בעזרת הפטנטים? – לא ברור. אפל לא נראתה טוב בהיבט זה ולאחר זמן קצר תרמה את הפטנטים לשימוש חופשי של W3C – בתנאי שיהפוך לחלק מהתקן ה HTML. כחלק מתנאי ההסכם – התקן אומץ במלואו (גם חלקים שהיוו כפילות ליכולות SVG).

כיצד בוחרים בין Canvas, SVG ו CSS3
נראה לי שרוב הנתונים הונחו כבר לפניכם, אך ברצוני לסכם:
ראשית, יתרון של SVG, Canvas ו CSS3 מול ספריות \"גבוהות יותר\" הוא שלא צריך לטעון קוד ג\'אווהסקריפט על מנת להשתמש בהן. הן סטנדרטיות לגמרי ומוכרות לכל.

CSS3 לא נועד לציור. אם אתם צריכים לעצב אלמנט קיים או \"לסגור איזו פינה\" – אפשר, אבל העובדה שאפשר ליצור ויזואליזציה ב CSS3 לא אומרת שבאמת כדאי.

SVG או Canvas
ל canvas יש יתרון משמעותי בביצועים. כאשר מדובר באנימציות או ציורים בעלי פרטים רבים – כדאי לשקול שימוש ב canvas. מסיבה זו בשימוש במשחקים כמעט ולא תמצאו SVG אלא כמעט ורק canvas.
בכל מקרה אחר – הייתי נוטה לבחור קודם כל ב SVG, אלא אם הוכח אחרת. תחזוקה של קוד canvas יכולה להיות קשה למדי (קוד עמוס פרטים ולא קריא) והיכולת לעבוד בכלי גרפי מתקדם על מנת ליצור חלק נכבד מהוויזואליזציה כ SVG מסייעת ליצור פתרונות מהירים ואמינים יותר.
עדכון: נתקלתי ב Responsive IDE שתומך ב canvas – כלומר מקום בו אתם יכולים לכתוב קוד ג\'אווהסקריפט ובכל פעם שתצביעו עם העכבר על ערך מספרי (מידה או צבע) תוכלו לגרור את העכבר, לשנות את הערך ולראות את התוצאה במקום. מדהים! אם אתם מציירים ב canvas זה יכול להיות שיפור פריון אדיר.

כמובן שגם SVG וגם Canvas הם כלים \"נמוכים\" בהם צריך לעבוד קשה על מנת להשיג מעט. אם מדובר בוויזואליזציה רספונסיבית ומורכבת – כדאי לעבור לרמות הפשטה גבוהות כגון Raphael, Processing או אפילו D3 או Fabric. אלו רק 4 ספריות וכמובן שיש עשרות ספריות נוספות לוויזואליזציה ב JavaScript היכולות להתאים למשימות מגוונות.

—-

[א] pixel = גרסך (גרגר מסך) בעברית תקנית – לחובבי העברית שבינכם.

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

סדרה: מבוא מואץ ל JavaScript ו jQuery – עבור מפתחי C# / Java מנוסים

את דרכי המקצועית התחלתי ב #C, ולאחר מכן עברתי את רובה ב Java.
כאשר נדרשתי להבין גם JavaScript ו jQuery מצאתי הרבה חומר טוב למתחילים, אך מעט חומר שהיה יעיל למפתח Java ותיק. כזה שכבר יודע לתכנת, יודע לולאות, מערכים או Exceptions – אבל לא מכיר ספציפית את JavaScript.

בסדרת פוסטים זו ריכזתי מבוא מואץ ל JavaScript ו jQuery שלא אמור לשעמם מפתחים מנוסים.

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

jQuery – מי, מה, מו?

להבין את JavaScript's this. המילה השמורה this בג'אווהסקריפט נשמעת דבר פשוט, אך היא מבלבלת למדי – עבור מי שהגיע מעולם ה Java (או #C או ++C או Pascal או …)

סקירה של יכולת מתקדמת של jQuery (מה שידוע כ Deferred Object), המסייעת לכתוב קוד אסינכרוני מסודר גם כאשר כמות הקוד היא גדולה.
בהצלחה!

מפרשן מול מהדר – מה ההבדל?

למי שלא עשה קורס ב"קומפילציה" (ואולי גם למי שכן), עולה מדי פעם השאלה: "מה ההבדל בין מפרשן (interpreter) למהדר (compiler)?"
– שניהם הרי גורמים לקוד של תוכנה לרוץ על מכונה כלשהי.התשובה הפשוטה היא כזו:
מהדר (Compiler) – מתרגם תוכנה שנכתבה בשפה "קריאה לאדם", כיחידה שלמה, לקוד בשפת מכונה (אפסים ואחדים) שמבוצע ישירות על המעבד.
מפרשן (Interpreter) – מריץ את התוכנה, שורה אחר שורה ו מפרשן כל שורה בנפרד. כל שורה עוברת תרגום "משפת אדם" לשפת מכונה – ואז מורצת, וחוזר חלילה.

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

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

מפרשן יכול לספק debugger בהשקעה אפסית כמעט – עוד יתרון. הקלות בכתיבת מפרשן יכולה להיות מתורגמת בקלות לכתיבת שפה cross-platform – יתרון נוסף.

כשעבדתי ב NICE, השקעתי כמה חודשים טובים בכתיבת מפרשן לשפה מוזרה ללא שם. זה היה אתגר מרתק ומעשיר, עד לשלב בו נתבקשתי לממש מצביעים (pointers). זה היה מספיק על מנת שאלחץ שוב, עד שהצלחתי לשכנע את המנהל שלי להשקיע כ 200$ ברכישת רישיון למפרשן JavaScript – שפה שמילאה בקלות אחר כל הצרכים והיה אלגנטית בהרבה (תארו לעצמכם).לכתוב מפרשן, זה בסה"כ דיי פשוט. קוראים שורה מקובץ (להלן ה"קוד"), מפרסרים מה כתוב שם (הכי פשוט שהפקודה היא המילה הראשונה) ואז מבצעים בשפה שלכם (#C למשל) את מה שרציתם לבצע. הדברים נהיים קצת יותר מורכבים כאשר יש משתנים, פונקציות, Scope ויותר. על המשמעות של הכנסת מצביעים אני לא יודע לספר.

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

אולי כדאי שבעצם נשאל את עצמנו מה היתרון בכתיבת מהדר? מדוע להשקיע מאמץ רב בתוכנה שתתרגם שפה לפלטפורמה יחידה, ושמציבה אתגר משמעותי בכתיבת debugger?

התשובה היא פשוטה: ביצועים. מהדר עובד קשה פעם אחת – אך הוא מספק קוד מכונה שיכול לרוץ בצורה אופטימלית ללא התערבות בזמן ריצה. מפרשן לעומת זאת – היא תוכנה שרצה ומפרשנת שורה אחר שורה. התקורה היא ברורה [א].

נוסטלגיה. כנראה שרוב קורסי ה"קומפילציה" בארץ נעשו על בסיס ספר זה מ 1986. מקור: amazon.com

המצב בפועל
בואו נבחן כמה שפות מוכרות: האם הן שפות-מהדר או שפות-מפרשן?

שפת אסמבלי
בואו נתחיל מאסמבלי: טעות נפוצה היא לקרוא לשפת אסמבלי (assembly) בשם "אסמבלר". אסמבלר הוא השם של המהדר שלה. האם האסמבלר "מתרגם תוכנה שנכתבה בשפה 'קריאה לאדם', כיחידה שלמה, לקוד בשפת מכונה (אפסים ואחדים) שמבוצע ישירות על המעבד"? כן. 
שפת אסמבלי היא בעצם ייצוג טקסטואלי, בעזרת מילים "בשפת אדם" כגון JMP ו MOV, לפקודות מכונה. האסמבלר פשוט עושה מין "Search and Replace" של "JMP" ל 00010010101101, למשל.

יש לי הסתייגות קלה מהגדרת שפת אסמבלי כ"קריאה לאדם". אני חושב שכדאי מאוד לעשות את ההבחנה בין "Human Readable" לבין "Human Parsable". ברוב הפעמים שמדברים על Human Readable (לדוגמה קובץ XML) רוצים לומר שזה טקסט שאדם יכול "לפענח", לאו דווקא "לקרוא".

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

שפת JavaScript
ג'אווהסקריפט הייתה במשך שנים דוגמה קלסית לשפת מפרשן. הסיבה העיקרית הייתה כנראה הרצון להריץ קוד שלה על סביבות שונות: כתוב את הקוד פעם אחת והוא ירוץ על ספארי (מק), FF (לינוקס) או IE (חלונות). אם הקוד קומפל לשפת מכונה אחת – הוא לא יתאים למכונה אחרת ולכן האפשרות היחידה היא לשלוח את קוד המקור למחשב עליו רץ הדפדפן – ושם לפרשן אותו.
בשנים האחרונות, הפכו נפוצים מהדרי JIT [ב] לשפת ג'אווהסקריפט. דוגמה בולטת היא מהדר ה JIT של V8 – מנוע  הגא'ווהסקריפט של גוגל. התוצאה: קוד המקור נשלח לדפדפן לפני ההרצה, אך הדפדפן מחליט להדר אותו ואז להריץ אותו בשפת מכונה – כך שבפועל כיום, JavaScript רצה מהודרת. הנה דוגמה ליתרון שבהידור. דמיינו את הקוד הנפוץ הבא:

var a.b.c.d.e.f.g.h.x = 100;
var a.b.c.d.e.f.g.h.y = 200;

כאשר a.b.c.d.e.f.g.h הם שמות namespaces או אובייקטים בג'אווהסקריפט. כאשר יש מפרשן, עליו לקחת את אובייקט a ואז לחפש בו property בשם b, ואז על b לחפש property בשם c וחוזר חלילה. ללא שיפורי ביצועים מיוחדים יכול להיות שמדובר בעשרות פעולות בזיכרון.
מהדר לעומת זאת קורא את 2 השורות ומבין שיש פה אופציה לייעל את הקוד. הוא יכול להפוך את הקוד ל:

var _p = a.b.c.d.e.f.g.h;
var p.x = 100;
var p.y = 200;

שיהיה יעיל בהרבה. מכיוון שג'אווהסקריפט היא שפה דינמית, עליו לוודא ש a עד h לא השתנו בעקבות ההשמה של x. כאן שוב יש מין "מיקרו מפרשן" שהמהדר מייצר ופועל בזמן ריצה.



שפת Java
Java היא שפת מהדר – נכון?
כן. בג'אווה יש מהדר שהופך קוד ג'אווה לקוד bytecode, אבל קוד ה bytecode עובר פרשון (מכיוון שהוא אמור להיות cross-platform). כבר מזמן יש JIT Compilers ל bytecode, כך שבפועל ג'אווה עוברת הידור ראשון בסביבת הפיתוח והידור שני על פלטפורמת ההרצה – מה שמתגלה כנוסחה יעילה למדי!
גם כאשר מהדרים קוד ++C למעבד מסויים (למשל מעבדי אינטל 64-ביט) יש לדגמים שונים יכולות מעט שונות – יכולות שלא ניתן לנצל בקוד המהודר מכיוון שעל המהדר לפנות למכנה המשותף הנמוך. הידור דו-שלבי כגון זה של ג'אווה מסוגל לבצע אופטימיזציות ייעודיות לחומרה הקיימת – ולנצל את כל היכולות / התכונות של החומרה.

שפת CoffeeScript
לקופיסקריפט יש מהדר, אך הוא עושה דבר מוזר: הוא מתרגם קופיסקריפט לג'אווהסקריפט. בשל הבא קוד הג'אווהסקריפט  (המהודר) נשלח למכונת משתמש-הקצה ועובר הידור ב JIT Compiler – כך שיש לו הידור כפול, אך קצת שונה משל ג'אווה מכיוון ששפת הביניים (intermediate language) היא ג'אווהסקריפט – שפה גבוהה בהרבה מ bytecode והיכולת לבצע אופטימיזציה בקוד – פחותה.
בפועל, אתם מבינים, יכולה להיות שרשרת דיי מורכבת של מהדרים ומפרשנים בדרכו של קוד תוכנה להיות מורץ. הדרך לתאר את כל האפשרויות הקיימות היא מורכבת, ויתרה מכך – כנראה לא מעניינת.


(DSL (Domain Specific Languages
סוג חדש של שפות שהופיע לאחרונה[ג] הן שפות מבוססות דומיין, כלומר שפות מתאימות לפתור בעיות מסוג מאוד מסוים: ויזואליזציה של נתונים בתחום מסוים, ביצוע שאילתות על נתונים, קביעת תצורה של Firewall וכו'.
לרוב לא מדובר בשפה חדשה לגמרי, אלא שפה ש"מורכבת" על שפה קיימת. אלו יכולים להיות פונקציות או אובייקטים שמתארים עולם מונחים שמקל על פתרון הבעיה ובעצם מהווה "שפה חדשה".

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

האם שפת השכר שלנו, "P" נקרא לה, היא שפת-מהדר או שפת-מפרשן?
אם היא ממומשת מעל Groovy, למשל, אזי אפשר להתייחס אליה כסוג של שפה-מפורשנת (לגרובי).
כלומר: שפה-מפורשנת (ע"י גרובי), שעוברת קומפלציה (לבייטקוד) ואז מקומפלת (ב JVM JIT Compiler) לשפת מכונה שכוללת מיני-מפרשנים (שגם הם קומפלו בדרך לשפת מכונה). באאאאההה.

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

בפרק הבא בסדרה: "מה ההבדל בין חומרה לתוכנה"?
האם חומרה היא "מה שניתן לגעת בו", או אולי ייתכן שחיווט של בקרי דיסק (בהחלט ניתן לגעת בחוטים) הוא סוג של תוכנה, בשפת DSL של טכנאי-מחשבים?

עדכון: הנה נתקלתי בבסיס נתונים שמהדר SQL ל ++C ואז לשפת מכונה.

—-

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

[ב] (JIT (Just In Time הוא מונח של "ייצור רזה" שאומר "עשה את הפעולה רק מתי שצריך – לא לפני כן". הרעיון הוא שאם עושים פעולה (כגון הזמנה של מלאי חדש למחסן) לפני הרגע האחרון – היעילות היא איננה מרבית. JIT Compiler הוא כזה שמקמפל את הקוד ממש לפני ההרצה – ולא לפני כן. יש בכך יתרונות וחסרונות – מצד אחד יודעים בדיוק את הסביבה בה הקוד עתיד לרוץ וניתן לבצע אופטימיזציות לפלטפורמה הספציפית (לדוגמה דגם המעבד), מצד שני כנראה שהמשתמש לא ימתין לאופטימיזיות קוד שאורכות זמן רב.

[ג] בעצם הוא קיים המון זמן, אבל המודעות לקיים – היא זו שהתפתחה לאחרונה + קיבלה fancy name.