Backbone.js (ספריית MVC) – אוספים

בפוסט הקודם, הצצה ל Backbone.js (בקיצור BB, ובלי לדבר על פוליטיקה) ראינו את שני הטיפוסים המרכזיים ביותר של ספריית BB. בפוסט זה ארצה להרחיב מעט את היריעה לטיפוס מרכזי נוסף שנקרא Collection שמסייע לארגן את המודלים במערכת.
להזכיר: מודל (model) מייצג אובייקט המתאר "יישות בעולם בו עוסקת המערכת", קרי Domain Object. לעתים קרובות נרצה להציג / לטפל בסדרה של מודלים. BB מאפשר ותומך בצורך זה בעזרת Collection שהוא אוסף של מודלים, המספק יכולות מתקדמות יותר מרשימה פשוטה.שייך לסדרה: MVC בצד הלקוח, ובכלל.

דוגמה בסיסית

בואו נפתח בדוגמה פשוטה:

אתם יכולים לזהות מודל מינימלי בשם Person ואת האוסף (Collection) בשם People – אוסף של Person.

1 – ההגדרה של model בבנאי של האוסף אינה נדרשת בשלב זה. היא משמשת רק כאשר משתמשים ביכולות ה REST המובנות ב Backbone.Model ו Backbone.Collection על מנת לסנכרן שינויים לשרת. איננו עוסקים ביכולת זו עדיין, אך אני מעדיף להגדיר את המודל לצורך הקריאות. הקונבנציה המקובלת ב BB היא לקרוא לאוסף בשם הרבים (s בסוף) של המודל, למשל Invoice ו Invoices.

2 – אנו יוצרים מודל (בצורה נאיבית, אציג דרכים יעילות יותר בהמשך) של מג'יק ג'ונסון ומוסיפים אותו לאוסף.

3 – לכל מודל (בעצם לכל אובייקט של BB) שנוצר ניתן מזהה גלובלי ייחודי, בפורמט "c". במקרה זה אני יודע שאובייקט של מג'יק ג'ונסון הוא השלישי שנוצר במערכת ועל כן אני יכול לאמת את ה cid שלו. זו הנחה שלא הייתי רוצה להסתמך עליה בקוד אמיתי.
cid הוא קיצור של Client Identifier, שנועד לסייע לזהות מופע של מודל בצורה ייחודית. ב BB מניחים שצד-השרת, בעקבות השימוש בבסיס נתונים כלשהו, יהיה לכל אובייקט זיהוי ייחודי. בטווח הזמן בו האובייקט נוצר בצד-הלקוח ועדיין לא נשמר בבסיס הנתונים וקיבל Id ייחודי מבסיס הנתונים, ה cid אמור להיות כלי העזר על מנת לזהות ייחודית את המודלים.

4 – הוספנו עוד מודל אחד לאוסף.

5 – אוסף של BB, באופן מובנה, יכול להשתמש utilities של underscore.js – שהם דיי שימושיים. הנה דוגמה של פונקציה בשם plunk שעוברת על כל אחד מהמודלים באוסף, ומכניסה לתוך מערך התשובה ערך של שדה שהוגדר, במקרה זה – כל שמות המשפחה. כמה נוח.
כן, underscore.js "מתעסק לנו" באובייקטים בסיסיים של השפה כמו "Object" או "Array"', במקרה זה הוא הוסיף את plunk ומתודות אחרות למערך. נכון, זה משהו שממליצים לא לעשות. לא לעשות באפליקציה – אך זה "מותר" לספרייה. תתרגלו.

Collection ו Views

הנה דוגמה מורכבת מעט יותר המציגה חיבור של Collection ל View:
View אחד עבור כל Person, ועוד View עוטף של האוסף People.

התוצאה, עבור אוסף מינימלי של 2 אנשים, תראה משהו כזה:

הכותרת "שני אנשים" שייכת לPeopleView, והשאר נוצר ע"י שני מופעים של PersonView. בלי שום Styling כמובן – בכדי לצמצם את גודל הדוגמה.

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

ב1 – אנו יוצרים אוסף בשם people ומזינים אותו בערכים. המשמעות של reset היא "החלף את כל הערכים הנוכחיים ברשימת הערכים הבאה" והיא האופן בו נרצה להזין ערכים כאשר אנו טוענים מצד-השרת רשימה מלאה של ערכים. אנו מזינים את people במערך של אובייקטים שכל אחד מתאר את המודל של הרשימה – Person.

ב2 – אנו יוצרים את ה PeopleView, ה View שיודע להציג את הרשימה וליצור את ה HTML הנ"ל. נראה את הקוד שלו מייד.

ב3 – כך מבצעים בדיקת-יחידה יציבה (לא-שבירה) עבור תוצאת HTML. טעות נפוצה היא להשוות את כל תוצאת ה HTML כ String לערך שהיה נכון בסוף רגע הפיתוח ובחנו שהוא תקין. הבעיה: כל שינוי ב markup – אפילו ריווח או תוספת תג לא משמעותית (למשל label) תשבור את הבדיקה – גם כאשר התוצאה עדיין תקינה. הדרך הנכונה לבדוק היא לאתר "עוגנים חשובים" ב HTML ולבדוק רק אותם. האומנות היא למצוא את רמת הפירוט האופטימלית.
אנו מאמתים את טקסט הכותרת
אנו מאמתים את מס' האלמנטים מסוג h2
אנו מאמתים שהטקסט של הראשון הוא "ג'ונסון, מגיק" – כלומר את הפורמט. eq היא המקבילה של jQuery ל index.
אנו מאמתים שאחרי ("~" ב css selector) הכותרת h2 הראשונה ישנו אלמנט p שמכיל את כתובת האימייל של זיגי. איננו רוצים "להפיל" את הבדיקה אם מחליפים את התחילית "email" באייקון קטן, למשל. אנו מתמקדים בנתונים ההכרחיים.

ב4 – אנו מבצעים reset לנתונים – עם רשימה באורך 3, רק כדי לוודא שהכותרת הראשית (h1) אכן תלויה באורך הרשימה.

הנה הקוד עצמו:

ההתחלה אמורה להיות מוכרת. פרקטיקה טובה היא להוסיף ערכי defaults למודל עבור כל שדה שאנו הולכים להשתמש בו ב View – אחרת אנו עשויים להיתקל ב Runtime Errors ללא fallback (למשל כשיש מודל עם age בלבד).

1 – לא התאפקתי והשתמשתי הפעם ב template engine – שהיא הדרך המומלצת ב BB כדי לייצר את ה markup עצמו. השתמשתי בצורה בסיסית ביותר בספרייה המובנה בתוך underscore.js (אתם יכולים לזהות ע"פ תחביר ה ERB/JSP), אבל אין שום מניעה לעבוד עם ספרייה אחרת כמו handlebars.js, mustache.js או dust.js.
אתם יכולים לשים לב שב template.people יש קו על התגית . הסיבה: התגית היא deprecated ב HTML5. הדרך ה"נכונה" ע"פ HTML5 לייצר קו תחתון הוא לעטוף ב span עם class שלי ואז להחיל עליו קו-תחתון בעזרת CSS. לצורך דוגמה זו אתעלם מזוטות שכאלה.

עוד נקודה מעניינת היא השימוש ב class (נקודה) ולא id (סולמית) על מנת לזהות את החלק שבו "יוזרקו" חתיכות ה markup של ה Person הבודד. על Id ב HTML להיות ייחודי בכל הדף ואיני יודע בוודאות אם ה PeopleView יוצג יותר מפעם אחת על הדף או לא. סיכוי אחר: אולי מישהו אחר ישתמש בid בשם people-list במקום אחר. על מנת להישאר סטנדרטיים יש להימנע משימוש ב id אלא אם אפשר לוודא שהוא אכן ייחודי – מה שלרוב קשה לדעת. הפתרון: שימוש ב  css class לזיהוי.

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

גישה פופולרית היום היא להקליד את ה templates עצמם בתוך ה HTML עצמו עטופים בתג ועם id ידוע מראש ואז להשתמש ב jQuery על מנת לטעון את ה template בזמן ריצה. ה template כמובן אינו משפיע על ה HTML – הוא רק "יושב שם מבלי להפריע". מדוע לעשות זאת?

  • כדי ליהנות בעת כתיבת ה template מה HTML Editor עם Syntax Highlighting ובדיקת שגיאות.
  • הצמדות לכלל שאומר: makrup נמצא בקובץ ה HTML – לוגיקה ב javaScript. כל ספריות ה template engine שציינתי למעלה הן "logic-less" ואינן מאפשרות כתיבת javaScript בתוך ה template, אלא רק לוגיקה רק בשפה הפנימית שלהן. היה נכון יותר לקרוא להן "javaScript-less", בעצם.

מדוע, אם כן, אני מעדיף בכל זאת לשמור את ה templates בתוך ה javaScript code?

  1. אני משתמש בIDE בשם WebStorm שמספק לי את היתרונות של HTML Editor בתוך קובצי הג'אווהסקריפט. החיסרון היחיד: חוסר החופש לייצר שורה חדשה ללא +.
  2. כתיבת ה templates בתוך ה javaScript מאפשרים לי להריץ את בדיקות-היחידה ללא קובצי HTML – מה שמאפשר להם לרוץ הרבה יותר מהר. התוצאה: אני מריץ בדיקות בצורה תכופה הרבה יותר – כי כולן רצות בשתי שניות בתוך ה IDE.

2 – ביצירה של ה PersonView אני מייצר את ה"אוטומט" של ה template. על מנת להשיג יעילות מרבית, ה template engine נוהגים לפרק את ה string של ה template לצורה מהירה יותר לשימוש. לרוב מקובל לקרוא למשתנה שמחזיק את התבנית פשוט "template", השתמשתי ב myTemplate על מנת לנסות ולהקל על ההבנה במקרה זה.

3 – כאן מבצועת ההפעלה של ה"אוטומט" של ה template engine על נתונים מתאימים, במקרה זה – המודל המדובר. ה"אוטומט" myTemplate הוא בעצם פונקציה שמרנדרת html markup ברגע שמזינים לה נתונים. אנו טוענים את התוצאה לתוך this.el שהוא, כאמור, העטיפה ב DOM ל View שלנו.

בואו נעבור עכשיו ל PeopleView המייצג את התמונה הכוללת:
4 – לאחר שרינדרתי את החלק של People ב HTML, הרי היא ה"מסגרת" (במקרה שלנו – רק כותרת), אני רוצה להזין את הנתונים של המודלים הבודדים. אני קורא ל $.this על מנת לבצע חיפוש בתוך ה markup של this.el בלבד, אחר המקום בו "אזריק" את ה Views של המודלים הבודדים. זוהי גרסה מקוצרת (ומומלצת) לכתיבת "(this.el).find('.people-list')$" (אני מניח שאתם מכירים jQuery).

5 – each של collection מאפשרת לי להריץ פונקציה עבור כל מודל באוסף – משהו כמו foreach.

6 – או יוצרים מופע של PersonView עם המודל ה person הנוכחי. אולי זה מרגיש קצת "כבד" לייצר View כל פעם ולא לייצר אחד ולעשות בו שימוש חוזר – אך זה בסדר.

7 – אנו משתמשים ב view על מנת לרנדר את ה markup ומוסיפים את ה "glueing markup", שבמקרה זה הוא רק תגית br. אנו רוצים לשמור את ה template של Person "נקייה" כך שנוכל להשתמש בה גם ללא ה PeopleView.

סיכום

בפוסט זה צללנו לדוגמה ריאלית קצת-יותר (מהפוסט הקודם) של קוד ב backbone – על מנת להבין כיצד קוד ב BB "מרגיש". אני מקווה שהצלחתי להעביר את התחושה, אם כי היא עדיין מינימלית למדי. כפי שראינו, BB לא עושה "קסמים" (כמו Ember או Angular) – אנו עדיין כותבים את כל הקוד, אך BB נותן לנו מסגרת, guidance ו utilities על מנת להגיע לשם.
על הדרך השלמנו את הפער אודות השימוש ב template engine, שהוא היבט מרכזי בספריות "javaScript MVC", והדגמתי כיצד לבצע בדיקות-יחידה ל View וראינו Views מקוננים. כל זה דחוס בפוסט לא כל-כך ארוך. אני מקווה ששמרתי על איזון נכון של "לא משמעמם" ו "ניתן להבנה".

הערות יתקבלו בשמחה!

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

הצצה מהירה על Backbone.js (ספריית MVC)

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

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

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

שייך לסדרה: MVC בצד הלקוח, ובכלל.

סמל, שאם תכתבו ב Backbone.js – תיתקלו בו עוד הרבה…

רקע

לא ניתן להתעלם מתרומתה הרבה של ספריית jQuery ודומותיה[ב] בעיצוב מקומה החדש של שפת ג׳אווהסקריפט. במעגל השני של ההשפעה (לפחות בצד-הלקוח) נמצאות ספריות שעוזרות בכתיבת אפליקציות צד-לקוח גדולות. ספריות אלו נקראות ״MVC Frameworks" או ״ספריות ארכיטקטוניות״. השם MVC עשוי לבלבל את מי שכבר מכיר ועבד עם ספריות MVC צד-שרת כגון Struts או ASP.NET MVC.

בעוד ספריות "MVC קלאסיות" מטפלות באפליקציות רבות דפים והניווט בניהם, ספריות ה "JavaScript MVC" לרוב מטפלות באפליקציות דף-יחיד (כגון GMail) בהן יש מסך עקרי אחד שחלקים בו מתחלפים / משתנים – אך אין ניווט משמעותי. בכלל, עצם הפעולה בצד-הלקוח, ב Ajax, בו אין refresh לדף בעת פעולה – משנה את כללי המשחק: מציב בעיות חדשות ופתרונות אחרים. עוד הבדל בולט הוא שברוב ספריות ה "JavaScript MVC" אין Controller. השם MVC בא לבטא את הרזולוציה ואת הבעיה שהן באות לפתור: ארגון קוד באפליקציית UI גדולה ומורכבת.

קיימות כיום מספר רב של ספריות "JavaScript MVC" וצפוי בהן עוד תהליך של קונסולידציה. שלושת הגדולות הן:

  • Backbone.js כנראה הנפוצה והבוגרת מכולן. מספקת מסגרת כללית בלבד ומשאירה חופש / חללים למפתח להחליט ולמלא בעצמו.
  • Knockout.js ששמה במרכז את ה Data Binding ומאפשרת לכתוב בקלות אפליקציות "מונחות-נתונים" (מערכות מידע וכיוצ"ב) נפוצה במיוחד בעולם ה NET.
  • Ember.js (לשעבר Amber.js), ספרייה מקיפה שכוללת גם Class System[ג] ושואבת רעיונות רבים מ Rails ״כתוב מעט קוד, אך עשה זאת בדרך שלנו" צעירה יחסית – אך זוכה למומנטום משמעותי. נפוצה במיוחד בקרב מתכנתי רובי.

ספריות משמעותיות אחרות הן Spine.js, JavaScriptMVC, Batman.js ו AngularJS (מבית גוגל). האתר המצוין todoMVC מציע השוואה מעמיקה בין האפשרויות בכך שמימש אפליקציית דוגמה בכל אחת מספריות הנ"ל (ועוד כמה ספריות נוספות).

רגע של התפלספות

כפי שציינתי, ספריות "JavaScript MVC" הן לא בדיוק MVC קלאסי. בעצם, גם ל"MVC קלאסי״ יש כמה פרשנויות שונות (לדוגמה, האם ה Controller מתמקד בעיקר בטיפול ב input או אחריות על ניהול הflow והתלויות?[ד]).

המדקדקים מגדירים את Knockout.js כ MVVM) Model, View, View-Model) – תבנית עיצוב שמייקרוסופט מקדמת בעולם הNET. זה דיי נכון.
את Backbone.js ו Ember.js נוהגים להגדיר כ Model, View, Presenter) MVP). האמת, ספריות כמו ASP.NET או GWT מתאימות יותר להגדרה זו. הגדרה כגון Model, View-Controller, Router (בקיצור MVCR) תהיה יותר מדויקת.

ההגדרה המועדפת עלי כרגע היא !Model, View, Whatever (בקיצור MVW או *MV).
התחלתי את הפוסט הזה בניסיון לעקוב אחר ההיסטוריה של תבנית העיצוב MVC, הוריאציות והשינויים שהיא עברה במשך השנים, בעיקר MVP ו MVVM, והניסיון להסביר כיצד ספריות ה "JavaScript MVC" מתאימות לתבניות אלו.

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

בסופו של דבר – MVC הוא ניסיון לארגן אפליקציית UI מורכבת בצורה שתהיה קלה לתחזוקה ושינויים. כל דור של UI (Web, Fat Client, Fully Client Side, Command Line) – והאתגרים שלו.
כפי שמרטין פאולר ציין פעם: "אנשים נוטים להסתבך עם MVC. החלק הכי חשוב הוא לבצע הפרדה אמיתית בין Business Logic ל UI. כל השאר – משני". אני מסכים ב 100%.

לכל ספרייה יש את הדרך שלה לארגן את הקוד – ואולי יותר מעשי ומעניין פשוט להתבונן בישומים קונקרטיים.
בפוסט זה אתן הצצה בסיסית ביותר ל MVW בג׳אווהסקריפט בדמות Backbone.js – הספרייה הנפוצה והמינימליסטית שיכולה להיות בסיס לימודי מצוין להבנת MVC בג׳אווהסקריפט.

אז מה יש ב Backbone.js?

אקצר מעתה ואקרא ל Backbone.js פשוט BB.
  • BB היא קטנה למדי. 700 שורות קוד בערך. 1400 שורות עם הערות.
  • BB משתמשת ב underscore.js (ספרייה טובה ל javaScript Utils מאותו היוצר) ו jQuery. אם אתם עובדים עם BB – יהיה לכם מאוד נוח להשתמש גם כן בספריות אלו, וקצת פחות נוח (אך אפשרי לחלוטין) להשתמש בספריות אחרות. BB מעודדת שימוש בספריית Templating – ואתם יכולים לבחור את זו של underscore (תחביר JSP-like, שלי אישית, עושה צמרמורת) או כל אחת אחרת.
  • ל BB יש תיעוד טוב (reference) ו Tutorials סבירים. יש קהילה גדולה ופעילה.
  • בגלל ש BB פשוטה – מייחסים אותה כמתאימה למערכות פשוטות יחסית – אך בפועל יש גם מערכות גדולות מאוד ומורכבות שנכתבו בעזרתה.
  • מאוד קל לעבוד עם BB מול שרת שמספק REST APIs – במיוחד אם תבנית-הנתונים היא JSON.
  • ל BB יש ארכיטקטורת Plug-Ins וניתן למצוא Plug-Ins רבים שמרחיבים את יכולות-הבסיס שלה.
  • BB מספקת מסגרת לא-מחייבת. אם אתם רוצים לפעול קצת אחרת, יש לכם את החופש לעשות זאת. כאשר מדובר במבנה של האפליקציה שלי – אני אוהב את זה.
  • כמו ספריות "JS MVC" אחרות, BB מספקת:
    1. מערכת-מחלקות (Class System) עבור Model ו Views (וגם Routers ו Collections – שהם רכיבים מרכזיים אחרים בBB)
    2. ניהול של המודלים (ה instances), כולל הקשרים בניהם.
    3. כלים לאימות ערכים (Validation Logic) במודל.

הנה דוגמה למודל מינימלי שניתן לייצר ב BB:

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

המודל שלנו, Person בדוגמה זו, הוא פשוט למדי. הייתי יכול ליצור אותו גם ללא defaults – רק עלי ידי הורשה ריקה מהמחלקה Backbone.Model. תכונות המודל (כמו שם פרטי/משפחה) הן דינמיות ומוגדרות בזמן יצירת האובייקט הספציפי. ה default היא היכולת לחייב שתכונות מסוימות יהיו בכל מקרה.

ב1 – אנו יוצרים מודל של אדם בשם אוזי כהן.
ב2 – בכדי להדגיש את השוני בינו ובין אמן אחר בשם ״ליאונרד״ ובעל שם משפחה זהה, אוזי משנה את שם המשפחה שלו.
ב3 – אנו מקבלים את המודל המלא בפורמט JSON.

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

השתמשתי במודל הכי פשוט שאפשר – כי ברצוני להתמקד ב View.

PersonView היא המחלקה שאחראית לצייר מודל של Person. יכולים להיות, כמובן, כמה Views שונים לאותו המודל. את ה markup שה View יוצר, ארצה לשלב היכנשהו ב DOM. בדוגמה זו – זה יהיה container$, שמשתמש ב span בכדי לא לבלבל עם ה div ש Backbone יוצר באופן אוטומטי לכל מופע של המודל.

el הוא בעצם אלמנט ה DOM שיצר לי BB כחלק מה View. הוא עושה זאת על מנת לאפשר ל markup בפנים להשתנות, בלי שאצטרך לחבר את ה markup שוב ל container. הבחירה ב div היא כמובן רק default, ואם אני זקוק ל list element (תג

  • ) – אפשר לקבוע זאת.ב1- אני בודק מה יש בתוך ה container – ניתן לראות את ה markup שנוצר בתוך הפונקציה render. זו דוגמה דיי מכוערת – לרוב נשתמש בספריית templating על מנת שהקוד שיוצר את ה html snippet יהיה אלגנטי. לא רציתי להעמיס על דוגמה זו ולכן הקוד ה"פרימיטיבי".

    ב2- אנחנו בוחנים את ה markup כולל ה container. רק לוודא שהתמונה הכללית ברורה.

    ב3- אנחנו מבצעים שינוי למודל ורואים שה markup ב container השתנה באופן פלאי. "פלאי" – כמובן שלא. אנחנו כותבים את הקוד שעושה זאת – backbone רק מכווין אותנו למבנה מסוים ועוזר לנו.

    בואו נבחן את הפונקציה initialize שהיא לצורך העניין הבנאי (constructor) של מחלקת ה PersonView שלנו, וכוללת 2 משפטים מוזרים:
    ב5- אנחנו מבצעים פעולת JavaScripts's bind לכל המתודות במחלקה שאנו הולכים ל"ייצא" כ callbacks לאובייקטים אחרים. במקרה זה – יש רק את ״render״, אך יכולתי באותה מידה לשרשר גם שמות של מתודות נוספות. למה צריך לעשות JavaScript bind? – כדי ש this ימשיך להצביע לאובייקט שלנו. מבולבלים? קראו את הפוסט להבין את JavaScript this.
    הסימן "_" (קו תחתון) הוא הקיצור לגשת לספרייה underscore.js, ממש כמו שניגשים עם $ ל jQuery. אלו המקומות ש underscore מסייעת מאוד לכתוב קוד ב BB. אתם לא חייבים – אבל כדאי.

    ב6- אנחנו "מחייטים" את ה View שלנו לבצע פעולת render כל פעם שהמודל משתנה (change' event'). מאוד דומה לאירועים ב jQuery.
    הבהרה קלה: אם אתם מכירים את תבנית העיצוב MVC אתם יכולים להבחין שה View ב Backbone מבצע פעולות ("חיוט") של Controller. זה נכון: אפשר לומר שה View ב BB הוא בעצם View-Controller: הוא עושה הרבה פעולות שבמקור יוחסו ל Controller.
    אם אתם מכירים את תבנית העיצוב MVP נראה שה View הוא בעצם יותר Presenter. הוא אחראי ל Presentation Logic. זה נכון: בעצם מה שנקרא "View" ב BB הוא דומה ל MVP's Presenter בעוד ספריית ה Templating (בעצם ה Template עצמו) – דומה מאוד ל MVP's View.

    סיכום

    הצצנו ל Backbone וטעמנו כיצד נראה קוד שמשתמש ב Backbone.js. טעימה קטנה.
    דיברנו גם על החשיבות של ספריות "JavaScript MVC" – כאשר קוד הג'אווסקריפט שלכם מתחיל לגדול ולהסתבך. וגם על החשיבות לא לחפור בתבנית-העיצוב MVC וההיסטוריה שלה יותר מדי 🙂

    ייתכן ואמשיך לכתוב קצת על Backbone על מנת להתעמק בו קצת יותר, בניסיון לבחון JavaScript MVC "מהשטח".

    בהצלחה!

    ——

    [א] נקראת Harmony, ג׳אווהסקריפט 2.0 או ECMAScript 6.0. כוללות, ככל הנראה, מחלקות, מודולים (חבילות של מחלקות עם ניהול תלויות), תיקונים לכמה כשלים של שפת ג׳אווהסקריפט ועוד. ממש מהפיכה!

    [ב] Prototype ו MooTools היו גם הן מועמדות ראויות, אך קהל המפתחים בחר לבסוף בjQuery. ניתן לקרוא על העיקרון מאחורי jQuery בפוסט מבוא מואץ ל jQuery.

    [ג] כלים / Framework להגדרת מחלקות וכל מה שקשור בהן. בג'אווהסקריפט, כפי מתואר בפוסט מחלקות בג'אווהסקריפט. בשפת ג'אווהסקריפט לא קיימות מחלקות – ועל המתכנת לייצר אותן לבד. בעוד ספריות "JavaScript MVC" רבות מספקות סביבה אחידה / מוגנת ליצירת מחלקות עבור האלמנטים הבסיסיים בספריה (כמו Model או View).- ספריית Ember מספקת כלים שתוכלו להשתמש בהם עבור כל המחלקות בפרויקט שלכם.

    [ד] המקור של MVC הוא בשפת Smalltalk בשנות ה-70. כשהגיעו ה Fat Clients (ב ++C), ולאחר מכן הווב – נוצרו פרשנויות ווריאציות שונות ל MVC על מנת להתמודד עם האתגרים החדשים שהציבו טכנולוגיות אלו.

מקביליות עם jQuery (ובכלל)

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

  • היכולת לארוז רק חלקים מהספרייה (אולי בגלל התחרות מ Zepto.js).
  • הפסקת התמיכה בגרסאות לא-חדשות של Internet Explorer.
  • איחוד מנגנוני הרישום לאירועים: bind, live ו delegate למנגנון ה on (חדשות ישנות יותר).
האמת שמדובר בשינוי שהוצג ב jQuery גרסה 1.5 (עם כמה תוספות ב 1.6 ו 1.7) – ואישית, אני לא זוכר שהוא עשה חצי מהמהומה של השינויים הנ"ל. בכל זאת זהו שינוי משמעותי וחשוב.
השינוי נוגע באזור ה Ajax ומאפשר גמישות שלא הייתה קיימת קודם לכן.
בואו נדבר עליו.

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

שינוי בפקודת ה Ajax
למי שלא מכיר, jQuery עוטף את ה XMLHttpRequest (או בקיצור XHR) של הדפדפן בצורה נוחה ומאפשר לקרוא בפשטות ajax.$ על מנת לבצע קריאת ajax. יש גם גרסאות מקוצרות בשם get.$ ו post.$.

עד גרסה 1.4 הדרך לבצע קריאת ajax הייתה כזו:
$.get('http://server.com/myurl', {
  success: onSuccess, // a callback function to be triggered on success
  failure: onFailure, // a callback function to be triggered on failure
  always: onAlways // a callback function that is triggered on either failure of success. Like "finally".
});

success, failure ו always הם callbacks שנקראים כאשר הקריאה מסתיימת, הצלחה או כישלון.

מגרסה 1.5 אפשר לכתוב את הקוד בדרך הבאה:
var request = $.get('http://server.com/myurl');
...
request.done(onSuccess);
request.fail(onFailure);
request.always(onAlways);

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

מה הטעם? האם הקוד הקודם לא נראה יותר ישיר ופשוט?

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

Making Promises
אפתח בכמה מילים גדולות: מה ש jQuery עשו, הוא לממש Design Pattern (יו… וואהו!) שידוע בשמות הבאים: Deferred Object או Promise או Future. יש אפילו הגדרה פורמלית בשם Promises/A שמגדירה כיצד תבנית זו אמורה להתנהג בג'אווהסקריפט.
לאכזבתם של לא-מעטים, jQuery לא נצמדה ל"הגדרה הפורמלית" ומימשה וריאציה קצת שונה של תבנית העיצוב, מימוש שהושפע כנראה מספריית Dojo שסיפקה יכולת דומה. אם אתם אורתודוקסים ל Promises/A, תוכלו למצוא מימושים "תקניים" בספריות כגון Q.js או Async.js שניתן להשתמש שהם. לרובנו, כל מה ש jQuery יעשו – מהווה את התקן בפועל.

על מנת להבין את המשמעות של הפעולה של ה Promise, נפרק אותה (Deconstruct) למרכיבים יותר בסיסים ונבחן אותם. הביטו על קוד ה JavaScript/jQuery הבא:

var deferred = new $.Deferred();
deferred.done(function(){ console.log('done'); }); 
deferred.fail(function(){ console.log('fail'); }); 
...
deferred.done(function(){ console.log('donedone'); });

האובייקט Deferred הוא לב העניין, אם כי אינו קשור ל ajax במאומה. הוא מעין handle-עתידי.
בעת קריאה ל ()deferred.resolve – יופעלו הפונקציות (ניתן לרשום מספר לא מוגבל של פונקציות, לכל אחד מהאירועים) שנרשמו ל done. במקרה זה – כתיבה של done ו donedone ללוג.
בעת קריאה ל ()deferred.reject – יופעלו הפונקציות שנרשמו ל fail. במקרה זה כתיבת fail ללוג.

ההתנהגות העצלה (deferred) נובעת, כפי שאתם בוודאי מבינים, מעצם רישום והפעלת פונקציות בג'אווהסקריפט. האובייקט Deferred הוא פשוט למדי ואינו כולל התנהגויות אסינכרוניות מיוחדות.

ה Promise הוא אובייקט (יחידון = singleton) שחוזר מקריאה ל ()deferred.promise וכל מטרתו היא לאפשר גישה מוגבלת: לאפשר לרשום פונקציות ל done, fail ו always – אבל לא לבצע triggering ל reject או resolve.

Read/Write Pattern – שליטה ב"הרשאות" ע"י הפרדת הפעולות ל interfaces שונים. אפשר להחליף את user ב promise ואת userMaint ב Deferred על מנת לראות את תבנית העיצוב בהקשר לדיון שלנו.

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

כעת, לאחר שהבנו את ההתנהגות של Promise, בואו נראה אלו בעיות מבנה זה יכול לפתור לנו.

Concurrent Pattern: Monitor
השימוש ב Deferred יכול לסייע לנו לבנות מבנה של concurrent programming בשם בקר (monitor). בקר הוא כלי לטיפול באסינכרוניות, הגבוה ברמת ההפשטה שלו מ mutex או semaphore, אך נמוך מ "synchronized" של ג'אווה / #C. אנו עשויים למצוא אותו דיי שימושי בשפת ג'אווהסקריפט / שימושי ב ajax בהם אסינכרוניות היא נפוצה [ב].

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

נאמר שאני מחכה בג'אוובסקריפט ל2 פעולות שיסתיימו: טעינה של נתונים מהשרת ואנימציית מעבר. רק ש2 אלו יסתיימו אני רוצה להציג dialog עם שאלה למשתמש.
הדרך הנאיבית לעשות זאת היא ש callback א' ישנה ערך גלובאלי כלשהו, ו callback ב' ימתין עד שהערך הגלובאלי השתנה. כאשר callback ב' מצא את הערך לו הוא ציפה – אני יודע ש2 הפעולות הסתיימו.
הבעיה?
הקוד קשה לקריאה: יהיה צורך לעקוב אחרי הלוגיקה של ה callbacks להבין מה הם עושים.
קרוב לוודאי שייוצר קוד מסורבל ומאוד לא נוח לתחזוקה.
הקוד לא יעיל מבחינת ביצועים.

הרבה יותר אלנגנטי, יעיל ונוח יהיה פשוט מאוד לכתוב:

var promiseA = $.get(...);
var deferredAnimation = $.Deferred(); // call  deferredAnimation.resolve() when it is done
var promiseB = deferredAnimation.promise();

$.when(promiseA, promiseB).

done(function(promiseAargs, promiseBargs) {
   ... // what happens when both are done 
});

תענוג!

מודולריות
השימוש העיקרי, והנפוץ יותר ל Promises הוא עבור callbacks פשוטים.
כפי שציינו קודם לכן, הקוד

$.get('http://server.com/myurl', {
  success: onSuccess, // a callback function to be triggered on success
  failure: onFailure, // a callback function to be triggered on failure
  always: onAlways // a callback function that is triggered on either failure of success. Like "finally".
});

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

במערכות שכאלו, מה שסביר שיקרה הוא אחד מהבאים:

  • ה onSuccess callback יפנה לאזורים שונים במערכת ויבצע שינויים. למשל: העלמת שעון חול, הפיכת כפתור ל enabled, הוספת פריט לרשימה. קרוב לוודאי שפעולות אלו ישברו את ההכמסה של המודולים במערכת.
  • אם ישנה מודעות מספקת להכמסה, קרוב לוודאי שכל אובייקט יכלול פונקציית callback שיודעת להגיב לאירוע ולשנות את הערכים המקומיים למודול. אבל… איך מעבירים את ה callback למקום בה נעשית הצורה האסינכרונית? לעיתים זה יהיה פשוט כי הגיוני שתהיה תלות בין המודולים – ולעיתים קרובות נאלץ להעביר את ה callback בין אובייקטים רבים ולהוסיף אולי תלויות על מנת לבצע את הקישור. מי שראה קוד שמעביר מספר callbacks בין אובייקטים, על מנת לטפל באירועים שונים או פשוט callbacks ממקורות שונים, יודע עד כמה הקוד עלול להיות מסורבל ו"ללכלך" את המערכת. התועלת בהכמסה עלולה לפחות – אם המחיר הוא קוד סבוך כל כך!
פתרון שבמקרים רבים יהיה אלגנטי למדי הוא שימוש ב promise בעת יצירת הקריאה האסינכרונית ותעבירו את ה promise לאובייקטים אחרים. אובייקטים אלו יוכלו להירשם לאירועים שמעניינים אותם, אבל מעבר לזה – להעביר את ה promise הלאה לאובייקטים אחרים על שמודול המקור לא מכיר. מה שנקרא "הכמסה". – כך שאובייקטים אחרים שהם מכירים יירשמו עליו.יש פה ייתרון במספר האובייקטים המועברים. אם 2 אובייקטים שונים היו רוצים לדעת על הצלחה או כישלון של אירוע שיצרתם – הייתם צריכים להעביר 4 callbacks. במקום זה אתם יכולים להעביר promise אחד בלבד.

סיכום 
Promise הוא לא Silver Bullet, אבל במקרים רבים הוא יכול להיות כלי רב-עצמה שיתיר סיבוכיות רבה מהמערכת. היכולת לנתק את השליטה (קריאות resolve ו reject של Deferred) מצד אחד ואת התגובה לסיום הפעולה (done, fail ו always של ה promise) מצד שני מאפשרות לשמור על מודולריות גבוהה של המערכת במחיר נמוך.

אם אתם מוצאים את תבנית העיצוב של Deferred ו Promise שימושית, יש עוד פונקציות שניתן לחקור:
Deferred ו Promise יכולים לתקשר בניהם גם אודות progress (שלא כיסיתי בפוסט). לפעולת when יש פעולה "אחות" בשם then. חשובה אולי מכולן היא פעולת ה pipe שמתירה לשרשר הצלחות / כישלונות של promise וכך לחסוך קוד.

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

[א] בגרסה 1.6 שונו שמות הפונקציות – אני משתמש בשמות החדשים. בגרסה 1.8 השמות הישנים (complete, success ו error) יוכרזו כמיושנים (deprecated) ולכן אני נמנע משימוש בהם.

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

מבוא מואץ ל JavaScript עבור מפתחי Java / #C מנוסים – חלק 2

בפוסט זה אני רוצה להתמקד במבנים שגורים בג'אווהסקריפט והבנה של המשמעות שלהם בפועל: אילו Tradeoffs אתם מבצעים כאשר אתם בוחרים בהם.שייך לסדרה מבוא מואץ ל JavaScript ו jQuery

מבוא 
האם JavaScript היא שפת Object Oriented? – תלוי את מי שואלים.
פורמלית, ג'אווהסקריפט מוגדרת כ Object-Oriented Language מול Class Oriented-Languages שהן ג'אווה ו#C. יש שוני שהדגש הוא על אובייקטים – לא מחלקות. בואו נשים את הפורמליסטיקה בשלב זה בצד.

התשובה שלי היא כזו: ג'אווהסקריפט היא יותר סט של כלים (toolkit) בו ניתן להשתמש בכדי לכתוב קוד OO. ניתן גם לכתוב בה קוד פונקציונלי ואולי אפילו גם קוד לוגי. השפה לא תחייב אתכם ל OO וכמעט לא תסייע או תנחה. זוהי גישה שונה למדי משפות כמו #C או Java שהן שפות OO מוצהרות ומכוונות את המפתח להשתמש בהן ככאלו.

אם תרצו לכתוב OO איכותי בג'אווהסקריפט – תדרשו להפעיל לא מעט משמעת עצמית. הקלות בה ניתן לבצע "תרגיל" (hack) בקוד ג'אווהסקריפט היא מדהימה, אנו נזדקק למנגנונים חברתיים (למשל Code Review) או טריגרים אוטומטיים (JSLint / JSHint) על מנת לשמור על קוד OO נקי ומסודר.

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

(function() {
  var n = 2;
  var addBy = function(num, x) { return num + x; }
  var multiplyBy = function(num, x) { return num * x; }

  n = addBy(n, 2);
  n = multiplyBy(n, 3);

  console.log(n);
}());

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

שימו לב לחשיבות להגדיר את המשתנים ואת הפונקציות הפנימיות בעזרת var. ללא var, הפונקציה / המשתנה יוגדרו ב global scope.
באפליקציות ג'אווהסקריפט, רוב הקוד שנכתוב יהיה private – אף אחד לא אמור לקרוא לו. אנו ניגש ל DOM, נייצר UI, נרשום אירועים ונגיב אליהם – את כל זה אפשר לעשות מבלי "ללכלך" את ה global scope. אין סיבה שלא תעטפו את כל הקוד האפליקטיבי שלכם, שאינו מספק שירותים לקוד אחר – בצורה זו.
עבור ספריות שבהן נרצה לחשוף פונקציות לשימוש חיצוני – הסיפור הוא אחר.

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

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

Prototype
בפוסט הקודם הזכרתי את המשתנה שנמצא על כל אובייקט ב JavaScript – הרי הוא ה __proto__, או בשמו המלא: ה Prototype. לג'אווהסקריפט יש מודל ייחודי לניהול אובייקטים שלא קיים בשום שפה נפוצה אחרת.

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

בואו נתבונן כיצד המילים השמורות prototype ו new בשפה מאפשרת לנו לנהל "אובייקטים":

var Calculator = function () { // constructor
  this.value = 0;
};

Calculator.prototype = { // prototype = Object Literal
  addBy: function (x) {
    this.value += x;
    console.log('value = ' + this.value);
  }
};

// alternate way to define method, less recommended
Calculator.prototype.multiplyBy = function (x) {
  this.value *= x;
  console.log('value = ' + this.value);
};

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

הורשה בג'אווהסקריפט מבוטאת ע"י שרשרת של קשרי prototype – לכל אובייקט יש אב שמסומן ב property בשם __proto__. ב default זה יהיה האובייקט Object – האב המשותף לכל האובייקטים בשפה. המילה השמורה prototype מייצגת את המשתנה __proto__ (שלא אמורים להשתמש בו ישירות, אך הוא שימושי ל debug).

בשלב הבא אנו מחליפים את ה prototype של הפונקציה Calculator (שהיה עד כה האובייקט Object) לאובייקט שאנו מגדירים (בעזרת תחביר של object literal). ההחלפה תשפיע על כל instance חדש שייווצר מ Calculator בעזרת המילה השמורה new.

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

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

לאחר הגדרה זו, אנו יכולים לייצר instances ולהפעילם בצורה הבאה:

var calc = new Calculator();
calc.addBy(4);
calc.multiplyBy(6); 
> 24
  • יצרנו מעין "מחלקה"* שניתן לייצר instances שונים שמתנהגים אותו הדבר. השימוש במילה "מחלקה" היא הרגל מג'אווה, הוא לא מדויק כאשר אנחנו מדברים על ג'אווהסקריפט. בעצם יצרנו אובייקט Prototype (אב-טיפוס) שבקריאה ל new ייווצר instance חדש שלו. סלחו לי אם אני ממשיך להשתמש ב"מחלקה" – הרי זה פוסט למפתחי ג'אווה / #C.
  • אנו יכולים להוסיף דינמית ל"מחלקה" זו מתודות או משתנים ע"י שימוש במילת ה prototype. האמת, כל אחד יכול. גם מפתח אחר שכותב קוד בקובץ אחר משלכם ומודע למחלקה בדרך-לא-דרך.
  • חשוב!: השימוש ב this בתוך המתודה, כמו addBy, הוא חיוני על מנת לגשת ל instance / אובייקט.
  • הקוד של פונקציות (addBy) נטענות לזיכרון פעם אחת בלבד. זה עשוי להישמע מוזר, אך במבנים אחרים, שנגיע אליהם עוד מעט, הגדרת הפונקציה תוכפל בזיכרון עבור כל אובייקט שנייצר. ההשלכה היא זמן יצירה ארוך יותר של אובייקטים ותפוסת זיכרון גדולה יותר – בעיה משמעותית אם אנו עומדים לייצר מאות או אלפי אובייקטים מאותו הסוג.
  • בעזרת השימוש ב Object Literal ה"מחלקה" מוגדרת בשני חלקים. ניתן גם להגדיר על פונקציה כהשמה חדשה ל Prototype וכך ליצור אובייקט שהגדרתו אינה בהכרח רציפה בקוד. רציפות זו כמובן רצוייה – ועל כן צורת ה Object Literal נראית לי עדיפה.
היכולת לשנות בזמן ריצה, בעזרת המילה השמורה prototype, מחלקות אינה מוגבלת למחלקות שכתב המשתמש. אדרבא, ניתן לבצע שינויים בכל זמן ועל כל מחלקה, גם מחלקות של השפה עצמה כמו String או Object. ניתן לשנות את Function – האב של כל הפונקציות, כפי שנעשה בדוגמה שהצגתי בתחילת הפוסט הקודם.

למרות שכמה ספריות נפוצות (למשל Prototype.js) עושות תרגילים שכאלו – זו נחשבת התנהגות לא רצויה ולא מומלצת בעליל. כדאי להימנע ממנה לגמרי.
רק להזכיר סיבוך אחד אפשרי: בג'אווהסקריפט אובייקטים הם "שקים" של properties (מעין HashTable או Map) – ולעתים קרובות אנו מתייחסים אליהם ככאלו. כל אובייקט יציג את ה properties שהוגדרו עליו ישירות וגם על כל שרשרת ה prototypes שהוא קשור בה. שינוי (הוספה / מחיקה) של property לאחד ה prototypes – יכול לשבור קוד קיים במספר תסריטים. ההמלצה היא לנקוט באחת משתי גישות:

  • בכל גישה ל property של אובייקט – לבדוק שה property באמת שלו (קריאת hasOwnProperty)
  • להימנע לחלוטין משינויים ב prototypes מלבד הגדרת ה"מחלקה".
אתם בוודאי מנחשים איזו גישה קלה יותר ליישום.

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

var Calculator = function () {
  // private members
  var value = 0;

  return {
    // public members
    addBy : function (x) {
      value += x;
      console.log('value = ' + value);
    },

    multiplyBy : function (x) {

      value *= x;
      console.log('value = ' + value);
    }
  };
};

var calc = new Calculator();
calc.addBy(4);
calc.multiplyBy(6); // 24

הבסיס כאן הוא דיי פשוט: נשתמש ב Closure על מנת לבצע הכמסה ונשתמש בערך החזרה מסוג Object Literal על מנת לאפיין את המחלקה.
הפונקציה Calculator (=קונסטרקטור) מגדירה משתנים ופונקציות בתוך עצמה, בתוך ה Closure. בנוסף היא מייצרת אובייקט (בעזרת Object Literal) שחוזר למי שקורא לה – במקום this. לא ציינתי קודם, אך קריאה ל new תחזיר את this רק במידה ולא הוגדר return value מפורש. אם הוגדר return – אזי הוא מה שיחזור.

שימו לב שבעזרת החזרה זו נוצר ההבדל מהדוגמה הקודמת: הערך החוזר מהקונסטרקטור הוא לא instance של הפונקציה Calculator עצמה, אלא אובייקט שאנו יצרנו ואנו שולטים בו. אובייקט זה מייצג את החלקים ה public של האובייקט שלנו. כמובן שבג'אווהסקריפט אין type safety ואין שום בעיה שקונסטרקטור יחזיר אובייקט מ"טיפוס" אחר.
כיוון שיש לנו פונקציה בתוך פונקציה, ויש reference מהפונקציה addBy למשתנה value – מובטח לנו שהמשתנה value ימשיך לחיות גם לאחר שהפונקציה Calculator הסתיימה. אם אתם לא זוכרים מדוע – חזרו להסבר על Closure בפוסט הקודם.

באופן זה, מי ששמר Reference לאובייקט שחזר יכול לבצע פעולות על האזורים הציבוריים, בעוד האזור הפרטיים "סגורים" בתוך ה Closure. עצם ההצבעה של ה Object Literal שחזר אל המשתנים הפנימיים – שומר על אורך חייהם.
שימו לב שמבנה זה, בניגוד לקודם, אין להשתמש ב this כדי להתייחס למשתנים של ה Closure.

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

מבנה זה הוא דיי מקובל ונקרא "Module".

הנה וריאציה קצת שונה של Module:

var myNS = myNS || {};


myNS.Calculator = function () {
  // private members
  var value = 0;

  var addBy = function (x) {
    value += x;
    console.log('value = ' + value);
  };

  var multiplyBy = function (x) {
    value *= x;
    console.log('value = ' + value);
  };

  return { // public parts (aka interface)
    addBy : addBy,
    multiplyBy : multiplyBy
  };
};

var calc = new myNS.Calculator();
calc.addBy(4);
calc.multiplyBy(6); // 24

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

בנוסף עשיתי עוד שדרוג קטן והגדרתי את המודול בתוך namespace. באופן זה אני מצמצם משמעותית את היכולת של ה Constructor להידרס ע"י מפתח אחר שגם במקרה בחר בשם Calculator. ייתרון נוסף ב namespace הוא ב Debugging, כאשר אוכל למצוא בקלות את המשתנים שלי בתוך ה namespace – ולא בערמה אחת עם כל משתני המערכת. ייתרון זה בא לידי ביטוי במיוחד כאשר יש כמה מפתחים על אותו הקוד. עדיין במודול יש Closure ומשתנים פרטיים של Closure לא נראים ברוב ה debuggers ללא breakpoint בתוך הקוד של יצירת ה Closure. לא נורא.

שימו לב שעלי להגדיר את myNS.Calculator ללא var – מכיוון שבג'אווהסקריפט אסורות הגדרות של משתנה עם "." בשם. הגדרתי את myNS מבעוד מועד – וחסכתי בעיות. ה namespace נדרש בקריאה לקונסטרטור – אך זו תוצאה רצויה ע"מ לוודא שאני באמת קורא לקונסטרקטור שלי, ולא של מישהו אחר.

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

כמה משחקים אחרונים
דוגמת ה prototype וה Module הן כנראה הדוגמאות הנפוצות. בכל זאת – לא תמיד הן יהיו בדיוק כפי שהצגתי. למתכנת יש יכולת לבצע עליהן וריאציות שונות שמשנות מעט את ההתנהגות. על מנת לחדד את החושים שלכם להבדלים הקטנים הללו – אציג כמה "מיני וריאציות" של ה Module וה Prototype. כשתתקלו בקוד אמיתי, דוגמאות אלו יכינו אתכם לכך שיש להתבונן בכמה פרטים ולא ישר להניח שאם אתם מזהים את המבנה הכללי – אזי אתם יודעים בדיוק במה מדובר.

כמעט מיותר לציין שחלק גדול מהקוד שכתוב ב JavaScript הוא:
א. מבנים פרימיטיביים ולא אופטימליים (אם ניתן לקרוא לזה מבנים)
ב. Copy-Paste של מבנים שחשבו עליהם – אך מי שהעתיק לא צלל למשמעות המלאה של המבנה. JavaScript Kiddies.

בואו נתבונן על הווריאנט הבא:

var calculator = function () {
  // private members
  var value = 0;

  var addBy = function (x) {
    value += x;
    console.log('value = ' + value);
  };

  var multiplyBy = function (x) {
    value *= x;
    console.log('value = ' + value);
  };

  return { // public parts (aka interface)
    addBy : addBy,
    multiplyBy : multiplyBy
  };
}();

שמתם לב להבדל? הוא קטן למדי ולכן הדגשתי אותו ב Bold.
הסוגריים בסוף הביטוי גורמים לכך שהמשתנה calculator מכיל את תוצאת הרצת הפונקציה ולא מצביע לפונקציה, כפי שהיה קודם. לכן, ע"פ קונבנציה, יש לקרוא לו calculator ב c קטנה.

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

ובכן, Singleton הוא דיי נפוץ בפיתוח אפליקציות Client Side. השינוי שביצענו בעצם הוא דרך לבטא Singleton. בניגוד לצד השרת בו דיי נדיר למצוא Singleton ברמת שפה – אנו לרוב מגדירים singleton ברמת הDependency Injection Framework או Service Layer. בג'אווהסקריפט לא נראה לי שיש דDI או Service Layer ואנו מגדירים Singleton ברמת השפה.

הקריאה לקוד, אם כן, תראה משהו כזה:

calculator.addBy(4);
calculator.multiplyBy(6); // 24

ניתן גם לממש Singleton בצורה יותר "קלאסית" (דומה לג'אווה): לאכסון את המצביע ל instance היחיד על ה prototype ולהחזיר אותו בפונקציית getInstance. אני חושב שהדרך שהצגתי פה היא משמעותית יותר נפוצה בעולם הג'אווהסקריפט. שימו לב לדקויות כמו אם שם הפונקציה מתחילה ב Capital Letter או האם יש סוגריים של הפעלה בסוף הגדרת הפונקציה. פעמים רבות הסוגריים לא יהיה ריקים אלא יגדירו פרמטר שמועבר לקונסטרקטור – אולי ערך שמחושב בזמן יצירתה.

הנה מבנה קצת מורכב שמשלב אלמנטים שונים מהדוגמאות השונות:

var Calculator = function () { // constructor
  // private fields
  this._value = 0;
};

Calculator.prototype = function () {
  // private functions

  var _addBy = function (x) {
    this._value += x;
    console.log('value = ' + this._value);
  };

  var _multiplyBy = function (x) {
    this._value *= x;
    console.log('value = ' + this._value);
  };

  return { // interface
    addBy : _addBy,
    multiplyBy : _multiplyBy
  };
}();

var calc = new Calculator();
calc.addBy(4);
calc.multiplyBy(6); // 24

במה מדובר פה?
האם אתם יכולים לעצור לדקה, לקרוא את הקוד, ולחשוב מה המשמעות שלו?

חזרנו למבנה מפוצל של קונסטרקטור לחוד וגוף האובייקט לחוד – שזה פחות טוב. מצד שני, בזכות השימוש ב prototype – החלק השני שמכיל את הפונקציות לא ישוכפל לכל אובייקט. יש לנו גם הכמסה והפרדה בין private ל public.

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

המשתנים שמוגדרים בתוך הקונסטרקטור צריכים להיות מוגדרים ביחס ל this – כך בפעולת new הם יהיו משויכים ל instance שנוצר. ללא this הם פשוט "ימחקו" בסוף הרצת הקונסטרקטור – לא דבר שאנחנו רוצים.
גם ההתייחסות בחלק הפרוטוטייפ צריכה להיות ל this – מסיבה זהה.

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

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

גם בשפת JavaScript עצמה יש עוד לא מעט ללמוד. אם שרדתם את המדריך הזה הייתי ממליץ להמשיך לאחד או יותר מהמקורות הבאים:
  • JavaScript Garden – ריכוז מצוין של נושאים מבלבלים / בעייתיים בג'אווהסקריפט. אתם תזהו כמה דוגמאות שלקחתי משם וקצת פישטתי.
  • Learning Advanced JavaScript – מדריך מאת ג'ון רזיג לקידום ספרו "סודות הג'אווהסקריפט נינג'ה". אם אתם זוכרים את קוד ה C-Syntax הלא ברור בעליל בתחילת הפוסט הראשון – מדריך זה הולך צעד אחר צעד להסביר אותו – ואתם אמורים להיות מוכנים "לרוץ" עליו.
  • Learning JavaScript Design Patterns – מדריך קצת יותר ארוך שעוסק במבנים בג'אווהסקריפט, גם הוא כאמצעי לקידום ספר שיצא בקרוב. כל עניין ונושא בשפה (למשל namespace) מוגדר במדריך זה כ "pattern" – אבל נו טוב, אני מניח שככה מוכרים הרבה עותקים של ספר תכנות.
אם אתם רוצים לבדוק את הקוד ולבצע debug בסביבה קצת יותר רצינית מהדפדפן, אז שווה לנסות את jsFddle או ישר לקפוץ ל IDE מלא כמו Netbeans, Aptana או WebStorm.
הערות / השגות / מחשבות, כרגיל, יתקבלו בשמחה.שיהיה לכם בהצלחה!