מקביליות עם 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.
הערות / השגות / מחשבות, כרגיל, יתקבלו בשמחה.שיהיה לכם בהצלחה!

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

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

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

 

למה JavaScript?

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

  • בעוד ה JVM של ג'אווה רץ על "כל שרת" – עובדה שסייעה לג'אווה להפוך למאוד נפוצה, ג'אווהסקריפט יכולה לרוץ על כל פלטפורמות ה JVM (בעזרת Rhino) ובנוסף – גם על דפדפנים ועל iOS -> שתי פלטפורמות אסטרטגיות שחשיבותן רק הולכת ועולה. תאהבו את זה או לא, אבל ג'אווהסקריפט היא שפת התכנות האוניברסלית ביותר שקיימת כיום.
  • ג'אווהסקריפט היא "מונופוליסטית": אין שפה אחרת שנתמכת ע"ג כל הדפדפנים החשובים.
    בניגוד ל JVM וה CLI שמריצים ByteCode ולא מודעים לשפה שבהם ה ByteCode נכתב (וכך נפתח פתח ל JRuby, סקאלה ועוד) JavaScript היא שפה מפוענחת (interpreted) והמנועים של ג'אווהסקריפט שפרוסים על הדפדפנים יודעים להריץ רק אותה.
  • ג'אווהסקריפט טובה לבניית UI. בניגוד לג'אווה שבמשך שנים ניסתה וניסתה – אך נכשלה, בג'אווהסקריפט ניתן לכתוב UI יפה ובצורה קלה. (על בסיס HTML ו CSS – כמובן).
  • לג'אווהסקריפט יש בסיס מתכנתים גדול. הרבה מפתחי #PHP, Ruby, C וג'אווה – יודעים גם קצת ג'אווהסקריפט. יש איזה ייתרון בלהיות שפה זקנה בת 17.
  • לג'אווהסקריפט אין הרבה מתנגדים. אין פה מלחמה בין אורקל למייקרוסופט, בין גוגל לפייסבוק וכו'. ג'אווהסקריפט נמצאת בעין הסערה.

ע"פ ה HTTP Archive, למשל, פרויקט שעוקב אחרת מגמות באתרי האינטרנט הגדולים, כמות הג'אווהסקריפט בשימוש באתרים שבמדגם זינקה ב 50% בשנה האחרונה בלבד.

כמה ג'אווהסקריפט יש בדפי אינטרנט של אתרים מפורסמים? הממ… זה גדל! מקור: pingdom.com

כיצד לומדים ג'אווהסקריפט?

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

טעות מס' 1: "אני מכיר את ה Syntax". זהו C-Syntax. אני אסתדר Along the way.

המציאות: Java ו JavaScript דומים אחד לשני כמו "Car" ו "Carpet": בעיקר בשם.
הקוד הבא אכן נראה מוכר למדי:

for (i = 0; i< set.length; i++) {
  set[i].doSomething();
}

האם אתם יכולים לומר בוודאות באיזו שפה כתוב קוד זה? אני בספק.

אם אתם מדמיינים כך את הג'אווהסקריפט שתתקלו בו – אתם טועים. מהר מאוד תתקלו בביטויים כמו prototype, הסימן $ שמופיע המון, משתנים עם קווים תחתונים __ ובכלל – סגנון זר ולא מוכר. זהו אמנם C-Sytnax – אך יש בו מספיק אלמנטים לא מוכרים על מנת לבלבל אתכם לגמרי.

למשל, האם אתם יכולים להסביר מה עושה הקוד הבא (שכתוב ב C-Syntax)?

Function.prototype.bind = function(){
  var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift();
  return function(){
    return fn.apply(object, args.concat(Array.prototype.slice.call(arguments)));
  };
};

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

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

קחו לדוגמה את האתר W3Schools – אתר reference לטכנולוגיות ווב המוכר. לאתר, מסתבר, אין שום קשר ל W3C והוא התבקש ע"י ה W3C, כמה פעמים, לשנות את שמו. מדוע?
האתר כולל שגיאות גסות בנוגע ל HTML, CSS וג'אווהסקריפט בכלל. אמנם הוא עושה רושם רציני – אך העובדות מדברות בשם עצמן. W3Fools – אתר שאוסף (חלק מ-) השגיאות וחוסרי הדיוקים של W3Schools, ממליץ למשל לחפש בגוגל עם "w3schools-", כלומר: רק תוצאות שלא כוללות את W3Schools. בהסתמך על הפופולאריות של W3Schools וכמות הפעמים שהוא מופיע בגוגל – זו עצה לא כל-כך רעה…

טעות מס' 3: "הבנתי. משהו פה לא עובד. נקרא את התיעוד הרשמי מקצה-לקצה כך שלא יהיו לנו הפתעות".

המציאות: כשלמדתי Java באמת הלכתי ל Spec – אך זו הייתה שפה ראשונה והייתי סטודנט צעיר. כשניסיתי לקרוא את התיעוד המומלץ של ג'אווהסקריפט – מצאתי את עצמי משתעמם מהר מאוד. רוב מה שכתוב שם הוא ברור מאליו למי שמכיר את ג'אווה או #C לעומק.

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

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

בהמשך הסדרה אני עוסק ב jQuery, מקור מקיף לגבי ה DOM הוא DOM Enlightenment. עוד חומר על פיתוח ווב תוכלו למצוא באתר העברי אינטרנט-ישראל.

כמה הבדלים בסיסיים בין JavaScript וג'אווה:

הערה: אני מקצר וכותב "ג'אווה" בלבד, אך מה כתוב כאן רלוונטי באותה מידה ל #C.

Scope
בג'אווה, Scope נוצר ע"י סוגריים מסולסלים, יהיה זה משפט if, לולאת for או סתם סוגריים שהוכנסו בתוך הקוד.
בג'אווהסקריפט Scope נוצר רק על ידי פונקציה. הנה דוגמה להתנהגות הצפויה (מקור: javaScriptGarden):

function test() { // a scope
  for(var i = 0; i < 10; i++) { // not a scope
    // do something
  }
  console.log(i); // 10
}

Global Scope
מה שלא מוגדר בתוך ה Scope של הפונקציה – שייך ל Scope הגלובלי. דריסות הדדיות הן תקלה שכיחה.

// Defined in Global Scope
foo = '42';

// Defined in "current" scope. Either function or global
var foo = '42';

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

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

weddingParticipantsCounter = 100;

function acceptParticipation(name) {
  weddingParticipnatsCounter = weddingParticipantsCounter + 1;
  alert('Registration Accepted: ' + name);
}

acceptParticipation("Mr. President");
acceptParticipation("The First Lady");
console.log(weddingParticipantsCounter); // -> 100?!

האם אתם יכולים להסביר מה השתבש? [א]

איך מתמודדים עם pitfall שכזה שבנוי בתוך השפה? כלי וריפיקציה בשם JSLint יספק הזהרה, אם אתם עובדים ב IDE שמריץ אותו תוך כדי כתיבה – יש סיכוי שתצליחו להימנע מכמה תקלות. הבעיה ב JSLint (או חברו JSHint) הוא שההזהרות שהם מספקים נכונות רק ל 90% המקרים ואתם נשארים עם רשימה של Warnings לא מטופלים – גם כאשר יש לכם קוד נפלא.
CoffeScript היא דרך נוספת להתמודדות. הוא לדוגמא מוסיף var מאחורי הקלעים לכל הגדרה.

Hoisting
חשבתם שהדברים מסתדרים? שימו לב לכלל הבא: הקוד בג'אווהסקריפט לא ירוץ ע"פ הסדר שבו הוא נכתב.
אני חוזר: הקוד בג'אווהסקריפט לא ירוץ ע"פ הסדר שבו הוא נכתב.

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

התבוננו על דוגמת הקוד הבאה:

var n = 1;

function foo() {
    if (false) { var n = 10; }
    console.log(n);
}

foo();

?מה אתם מצפים שתהייה התוצאה שתכתב ללוג?

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

הקוד שבעצם רץ, לאחר פעולת ה hoisting, הוא זה:

var n;

function foo() {
    var n;
    if (false) { n = 10; }
    console.log(n);
}

n = 1;
foo();
הגדרת המשתנה n, גם בתוך הפונקציה, עולה למעלה. זכרו שהסוגרים המסולסלים של משפט ה if לא מגדירים Scope חדש. כחלק מחוקי ה hoisting, ההשמה נשארת במקומה.
n מוגדר ב scope של הפונקציה ולכן הוא "מחביא" את n שהוגדר ב global scope.
כיוון שהתנאי "false" לא מתרחש במקרה זה, n נשאר undefined עד לנקודה בה הערך נכתב ללוג.
מה עושים?
שומרים במשמעת ברזל על הכלל הבא:
  • כל הגדרות ה var וה function יהיו תמיד בראש ה scope הנוכחי, אם זה הקובץ (גלובלי) או הפונקציה.
אחד הטיעונים שאני זוכר שהועלו כנגד שפת פאסקל הוא "למה אני צריך להגדיר את כל המשתנים בראש הפונקציה? למה הקומפיילר לא יכול לעשות את זה בשבילי?!". על טיעון זה בלבד אני זוכר כמה אנשים שהתרחקו מהשפה.
הכלל של הגדרת משתנים מראש הוא מובנה בשפה – ומתכנתים לומדים אותו בשיעורים הראשונים על השפה.בג'אווהסקריפט ניתן להגדיר משתנים בכל מקום – רק אחרי חודשים רבים של עבודה בשפה מגיעים להבנה שפשוט לא כדאי. השפה לא מספקת שום אזהרה או עצה. בכל זאת, לא שמעתי מתכנת ג'אווהסקריפט אחד מתלונן על הנושא הזה.

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

אם כן – זה יכול להיות Case Study טוב ב Pre-Sale. אני מניח שלא מעט תוכנות ארגוניות נהנות מהמנגנון הזה.

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

אני יודע (ע"פ סטטיסטיקות של Google Analytics) שאם אתם קוראים את הבלוג הזה יש סיכוי של 50% שאתם משתמשים בכרום, ועוד כ 20% שאתם משתמשים ב FireFox. לשני הדפדפנים הללו יש סביבת פיתוח לא רעה בכלל לג'אווהסקריפט שניתן להתחיל להשתמש בה מיד. אם אתם עדיין תקועים עם IE – השיגו לכם כרום. הגיע הזמן.הנה דוגמא פשוטה כיצד להריץ קוד ג'אווהסקריפט אם אתם רצים בכרום. אתם יכולים לעשות זאת מבלי לאתחל את הדפדפן. פשוט פתחו טאב חדש, לחצו על הכפתור הימני של העכבר בתוך הדף, בחרו את "Inspect Element" – כניסה מהירה לכלי הפיתוח של כרום, עברו ל Console והנה יש לכם Interpreter ג'אווהסקריפט איכותי:

הנה אני מקליד בו איזו פיסת קוד:

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

אובייקטים ופונקציות


Object Literal
{};

.כך יוצרים בג'אווהסקריפט אובייקט. זהו אובייקט ריק

בפועל אובייקט הוא רשימה של תכונות:

var myObj = { a: "Hello", b: "World" };
אם התחביר נראה לכם מוכר, נסו להיזכר מהם ראשי התיבות של JSON. הא!כבר נתקלנו בפונקציות, אך יש כמה עובדות חשובות נוספות שכדאי להכיר לגביהן.

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

function foo(text) { console.log(text); }
foo.x = 4;
foo(foo.x);

שידפיס כמובן את הערך 4.

Anonymous Functions
ג'אווהסקריפט בדפדפן היא שפה עשירה מאוד בשימוש בevents ולכן התחביר של Anonymous Functions הוא קצר ונוח. לדוגמה:
obj.doSomething(function(event) { console.log(event.getMessage()); });
הדרך המומלצת להגדרת פונקציות בג'אווהסקריפט היא להגדיר פונקציות אנונימיות ולבצע השמה למשתנה:
var foo = function(text) { console.log(text); };
בלינק זה תוכלו למצוא כמה דוגמאות מה יכול לקרות לכם אם לא תעשו זאת (מוהא-הא-הא!).
על מנת למנוע דריסה של פונקציות, אפשר להשתמש ב namespace:
var myNS = myNS || {}; // if namespace myNS exists - use it, else create a new namespace.
myNS.foo = function(text) { console.log(text); };

Closure

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

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

function simpleFunction() {
  var x = 4;
}
// x no longer exists
כאשר יש לי פונקציה פנימית, בתוך הפונקציה, שקוראת למשתנים – הם ישארו לחיות לעד. אני מנחש שזה עניין של pointer counting. הנה דוגמה:
function Counter(start) { // closure
  var count = start;
  return function() { // function with scope
      return ++count;
  }
}

var c = Counter(4);
// count still exists
c(); // 5
c(); // 6

המשתנה c מחזיק reference לפונקציה הפנימית של Counter, והיא מצידה מחזיקה reference למשתנה count – וכך הוא נשמר בחיים.

בואו נסתכל בדוגמא לבעיה שיכולה להיפתר ע"י שימוש ב Closure.

בדוגמת קוד תמימה זו, אני משתמש ב anonymous function מהסוג שהזכרנו קודם לכן על מנת לממש Listener. זוהי פעולה נפוצה למדי בג'אווהסקריפט. אני מגדיר פונקציה פשוטה למדי שתכתוב לערכים לlog.
התוצאה הצפויה היא, אם כן, ספירה מ 1 עד 15 ל log.

var listeners = []; // array

// fill up some listeners
for (var i = 0; i < 15; i++) {
  listeners.push(function() { console.log(i); });
}

// trigger an event to all listeners. Each one counts.
for (var j in listeners) {
  listeners[j](); // execute
}
התוצאה בפועל היא 15 פעמים המספר "15".
הסיבה לכך היא כזו: הפונקציה שנוצרת בתוך לולאת ה for מתייחסת למשתנה i, אבל i הוא משתנה בסביבה הגלובלית. נזכיר שסוגריים מסולסלים לא מייצרים scope. בנוסף – הפונקציה אינה מחושבת בזמן ההגדרה, אז בפועל נוצרות לנו במערך ה listeners חמש עשר עותקים של פונקציה שתוכנן הוא (console.log(i.כיוון שהן מופעלות רק בסוף קטע הקוד, i שהוא משתנה גלובלי – בעל הערך 15, כך שיודפס 15 פעמים הערך 15.
גם יצירה של 15 פונקציות בזיכרון הוא בזבוז לא קטן שהיינו מעדיפים להימנע ממנו.

הדרך לפתרון היא להוסיף Closure שישמור (או "יקפיא") את ערך המשתנה i, עבור הרגע בו הוגדרה הפונקציה:

var listeners = []; // array

function eventHandler(i) { // outer function = closure
  return function() { // inner function
    console.log(i);
  }
}

// fill in the listeners (syntetic)
for (var i = 0; i < 15; i++) {
  listeners.push( eventHandler(i) );
}

// trigger an event to all listeners. Each one counts.
for (var j in listeners) {
  listeners[j](); // execute listener
}

בדוגמה זו, הפונקציה eventHandler מבוצעת בתוך הלולאה. הערך i עובר אליה כפי שהוא, ואז נלכד "לנצח" בתוך ב closure. טוב, לא ממש לנצח – אולי עד שנגלוש לדף ווב אחר.

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

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

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

מקווה שנהניתם!

פוסט ההמשך בו אדבר על מבנים בג'אווהסקריפט ומשמעותם.

[א] קצת אכזרי, אך זו מהמציאות: weddingParticipnatsCounter = weddingParticipantsCounter + 1. ג'אווהסקריפט זיהה משתנה לא מוכר ובמקום להטריד את המתכנת – טרח והגדיר אותו בשבילו. איזה יופי!
IDE טוב היה מספק value highlighting שאולי, היה יכול לעצור מוקדם את התקלה.

[ב] האם זו טעות תכנונית שתעלה למשק העולמי 2 מיליארד דולר?

טרנדים חמים בתחום ה Web וה Mobile – חלק 3 ואחרון

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

CSS Meta-Languages
אתם בוודאי מכירים את \"שפת\" CSS – שפת העיצוב ה StyleSheets של האינטרנט. מצד אחד, CSS היא שפה רבת יכולות גרפיות, אך מצד שני קשה לקרוא לה \"שפה\". CSS היא שפה דקלרטיבית (כלומר: המתכנת מציין את התוצאה הרצויה – לא את הדרך להגיע אליה), והיכולות ה\"תכנותיות\" (אימפרטיביות) שלה – אינן עומדות בקו אחד עם שפות תכנות בסיסיות… משנות השישים של המאה הקודמת.

לדוגמה: ב CSS3, הוצגה לראשונה פונקציה חישובית שמאפשרת לבצע חישובים אריתמטיים. במקום שהמתכנת יפתח מחשבון, יקיש ערכים, יחשב ויקליד את התוצאה בתוך קובץ ה CSS – יהיה ניתן להקליד את החישוב בתוך פונקציית Calc ו\"שפת\" ה CSS תחשב אותו. אני לא מדבר על הכפלת מטריצות לוגרתימיות, אני מדבר על פעולה פשוטה כמו 8px+2px+4px.

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

\"מה כל-כך רע בלבצע חישוב קטן בראש?\" – אתם עשויים לשאול. \"תפעיל קצת את התאים האפורים שלך!\".

ובכן – אני מדמיין את קובץ ה CSS, חלק אינטגרלי מאתר או אפליקציה, שמכיל את הערך:

;width: 531px

…531 פיקסלים. איך לעזאזל הגעתי ל 531 פיקסלים?!
האם זה 600 פחות X, Y ו Z כפול 2?
האם 551 פיקסלים, שני אלמנטים למעלה, צריכים גם הם להשתנות אם יום אחד אחליט להרחיב את \"531 הפיקסלים\"? מה עוד צריך להשתנות ולמה?

דחף ראשוני של מתכנת טוב הוא להוסיף הערה:

width: 531px; // box is 560px wide – 2*padding (10px) – icon width (9px)
זה באמת יותר ברור.

יותר ברור – נכון, אבל האם אתם מדמיינים קובץ CSS עם מאות שורות בהערות, שניתן לתחזוקה?

אני שומע בראשי את Uncle Bob זועק בסרטוני הווידאו שלו \"!!Comment is a Failure\" – \"אם  כתבת הערה, סימן שנכשלת, לכתוב קוד ברור מספיק על מנת שיחיה בפני עצמו\" (הרמת הקול היא במקור).

כתבתי מספיק הערות במשך חיי שיצאו (דיי מהר) מסנכרון עם הקוד ונותרו חסרות פשר, על מנת לדעת שהוא צודק. האם תהיה ישועה או האם תכנות ווב תמיד ירגיש \"חאפרי\", מול תכנות צד-שרת?
תמונה מייצגת של דוד בוב, כשהוא במצב-רוח טוב. מקור: 33degree.org
היו כמה ניסיונות להתמודד עם הבעיה בעזרת jQuery (או ספריות דומות): במקום לשכפל קוד לא קריא בקובץ CSS – העבירו חלק נכבד מ\"קוד\" העיצוב ל jQuery (שיכולות ה Selection שלו מקבילות כמעט ל CSS). קוד jQuery נטען בעליה והחיל סגנונות עיצוב על גבי ה HTML. באופן זה ניתן להשתמש ב JavaScript שמספק יכולות מתקדמות למדי לשימוש חוזר בקוד.

הגישה היא נחמדה – עבור המפתח, אך יש מעט מאוד מעצבים גרפיים שיסכימו להתעסק עם קובצי JavaScript עבור משימת העיצוב. כמו כן יש השלכת ביצועים לא קטנה: jQuery צריך לטייל על ה DOM בזמן ריצה – פעולה יקרה למדי.
בשנת 2007 שוחררה ספרייה חדשנית שהתהדרה בססמאות אלגנטיות. קראו לה Sass [א].
Sass היא שפה חדשה שמאפשרת לכתוב StyleSheet אלגנטיים שקלים לתחזוקה. מכיוון ששום דפדפן באותו הזמן לא תמך בקובצי Sass (מצב זה לא השתנה מאז) – הפרויקט כלל גם קומפיילר שהופך את קבצי ה Sass לקובצי CSS מוכרים.
הלוגו של Sass: קיצור של Syntactically Awesome Stylesheets. הספרייה יועדה למעצבים – ולכן השיווק שמתאים יותר לפלח שוק זה. מקור: http://sass-lang.com/
מי שיתבונן בקובצי ה Sass יזהה אלמנטים מוכרים משפת רובי, ואכן משם ההשראה. מתכנתי Ruby on Rails הם אלו שהמציאו את Sass. באתר הבית של Sass תוכלו למצוא דוגמאות למכביר כיצד Sass הופכת את כתיבת ה StyleSheets למלאכה פשוטה ומהנה יותר. הנה כמה דוגמאות:
  • קבועים (נקראים בטעות \"משתנים\") אשר מאפשרים לקבוע ערך פעם אחת ולהשתמש בו הרבה פעמים. שינוי עתידי ידרוש שינוי במקום אחד בלבד: הגדרת הקבוע.
  • Mixins (מקבילות דקלרטיביות לפונקציות) – היכולת להגדיר פעם אחת מספר שורות שחוזרת על עצמן ולהשתמש בהן שוב ושוב. שימוש נפוץ הוא \"לעטוף\" בפקודה אחת את הצורות השונות בהן קוראים לפקודה בדפדפנים שונים, כך שהקוד יציג רק את הפעולה הרצויה מבלי לשכפל את התחביר של הדפדפנים השונים. rounded-corners או gradient הן דוגמאות טובות. 
הנה דוגמה איך נראה Mixin בפועל:
.gradient(@color, @start, @stop) {
  background: @color;
  background: -webkit-gradient(linear,left bottom, left top,color-stop(0, @start), color-stop(1, @stop));
  background: -ms-linear-gradient(bottom, @start, @stop);
  background: -moz-linear-gradient(center bottom, @start 0%, @stop 100%);
}


בהמשך, אוכל פשוט לקרוא ל:
.gradient(#F5F5F5, #EEE, #FFF);
על מנת להחיל גרדיאנט שתומך ב 3 דפדפנים (כל אחד עם תחביר מעט שונה) או רקע קבוע אם גרדיאנט לא נתמך.


Sass מציגה עוד יכולות חשובות, כגון nesting (היכולת לסדר את הקובץ ע\"פ מבנה ה markup ולצמצם התנגשויות אפשריות ב CSS), חלוקת הקוד לקבצים מבלי לגרוע בביצועים, פונקציות חישוביות, לוגיות או ניהול צבעים. פונקצית ()opacify, למשל, מאפשרת לכם להוסיף שקיפות לצבע מבלי לפרק אותו לערוצי ה RGB, כפי שנדרש בפונציה הסטדנדרטית של CSS – הרי היא rgba.
על גבי Sass קיימת ספריה עשירה של mixin בשם Compass שמציעה סט יכולות מגוון ובוגר. את הכל יכולתם לכתוב בעצמכם – אך ה mixins של Compass כבר עברו שיפורים רבים.
שפת Meta-CSS נוספת, דומה להפליא ל Sass, נקראת LESS – לא אתפלא לגלות שהיא Fork של Sass בימים מוקדמים יותר. הצעד הגדול ביותר שלה היה להחליף את הקומפיילר של Ruby בו משתמשת Sass בקומפיילר שנכתב ב JavaScript. ישנן עוד כמה שוניות פחות משמעותיות.
השפה האחרונה שאזכיר, פופולרית פחות מהשתיים שהוזכרו לעיל – אך עדיין נפוצה, היא ספרייה בשם Stylus. היא כבר עברה לתחביר נקי מכל תו מיותר (הושפעה מ Haml. כך גם התחילה Sass – אך היא החליטה לחזור לתחביר דומה יותר ל CSS). אני אישית מוצא את התחביר שלה יותר מדי \"hardcore\" מכדי להציג אותו בפני מעצבים גרפיים.
JavaScript Meta-Languages
הרשו לי לחסוך לכם קריאה מיותרת: אותו התהליך שהתרחש עבור CSS – קרה בערך במקביל גם בשפת JavaScript. שפות \"מטא\" שאינן תואמות ל JavaScript עצמה, אך מציגות שיפורים שונים, ומתקמפלות בסופו של דבר לקוד JavaScript סטנדרטי.

השפה הנפוצה בתחום היא ללא ספק CoffeeScript, תחת הסלוגן \"Expose the good parts of JavaScript\".
בניגוד ל Sass ו LESS שבאות להתמודד עם חוסר-יכולת-ביטוי של שפת ה CSS, שפת JavaScript עשירה למדי ומלאה ב\"שטיקים\" המאפשרים גמישות רבה למתכנת. שפות המטא של JavaScript באו דווקא לעשות את ההפיך: להגביל את JavaScript ולהגן על המתכנת בפני שורה של Pitfalls אפשריים.

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

לדוגמה, במקום לכתוב:

var Animal, Horse, Snake, sam, tom,
  __hasProp = {}.hasOwnProperty,
  __extends = function(child, parent) {
    for (var key in parent) {
      if (__hasProp.call(parent, key)) child[key] = parent[key];
    } function ctor() { this.constructor = child;
  } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };

Animal = (function() {

  Animal.name = \'Animal\';

  function Animal(name) {
    this.name = name;
  }

  Animal.prototype.move = function(meters) {
    return alert(this.name + (\" moved \" + meters + \"m.\"));
  };

  return Animal;

})();

Horse = (function(_super) {

  __extends(Horse, _super);

  Horse.name = \'Horse\';

  function Horse() {
    return Horse.__super__.constructor.apply(this, arguments);
  }

  Horse.prototype.move = function() {
    alert(\"Galloping…\");
    return Horse.__super__.move.call(this, 45);
  };

  return Horse;

})(Animal);

אפשר לכתוב בקופיסקריפט:

class Animal
  constructor: (@name) ->

  move: (meters) ->
    alert @name + \" moved #{meters}m.\"

class Horse extends Animal
  move: ->
    alert \"Galloping…\"
    super 45

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

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

עוד ביקורת אפשרית הוא שאולי, אבל אולי, קופיסקריפט לא הולכת רחוק מספיק: קופיסקריפט משפרת את התחביר, מונעת טעויות ג\'אווהסקריפט נפוצות (למשל הגדרה של משתנה ללא \"var\" – כך שהוא הופך להיות גלובלי או שימוש ב\"==\" ולא ב \"===\") אך היא עדיין לא מאפשרת קפיצת מדרגה ביכולת ה static checking של השפה. כמתכנת צד-שרת עדיין חסרה לי היכולת להגדיר משתנה שיספק לי Type Safety או בכלל לתפוס עוד טעויות בעזרת הקומפיילר.

שפה אחרת, עם מאפיינים דומים לקופיסקריפט, שכן עושה צעד נוסף בכיוון זה היא Dart של גוגל. יעד מרכזי של השפה הוא לאפשר לקוד להתחיל לרוץ גם לפני שכל הקוד נטען – וכך לספק פידבק מוקדם למשתמש. זמן טעינה ארוך ללא תגובה היא בעיה ידועה של אפליקציות גדולות בג\'אווהסקריפט.
דפדפן כרום [ג] יודע להריץ את Dart ללא קומפילציה ל JavaScript וכך להשיג ביצועים אפילו גבוהים יותר. כרגע לא נראה שדפדפנים אחרים ימשיכו בקו זה.

class Point {
  num x, y;
  Point(num this.x, num this.y);
  Point scale(num factor) => new Point(x*factor, y*factor);
  num distance() => Math.sqrt(x*x + y*y);
}

void main() {
  Point a = new Point(2,3).scale(10);
  print(a.distance());
}

הנה דוגמה לשפת DART: תחביר קרוב יותר ל C-Syntax שנהוג בג\'אווהסקריפט אך עדיין עם קיצורים רבים. \"num\" הוא תחליף ל \"var\" שיספק לי גם בדיקה בזמן קומפילציה.

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

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

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

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

background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(180,221,180,1)), color-stop(17%,rgba(131,199,131,1)), color-stop(33%,rgba(82,177,82,1)), color-stop(67%,rgba(0,138,0,1)), color-stop(83%,rgba(0,87,0,1)), color-stop(100%,rgba(0,36,0,1))); 
כמובן שזה רק דפדפן ואחד ויש בערך 6 צורות מעט שונות להגדיר את אותו הגרדיאנט לדפדפנים שונים.

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

  • colorzilla gradient-editor הוא הכלי שייצר את קוד הגרדיאנט למעלה וישמח לייצר קוד שמתאים לכל הדפדפנים.
  • CSS Layout Generator יסייע לייצר Layouting בעזרת CSS בלבד – פעולה שעשויה להיות מלאכת מחשבת
  • השם עשוי להפתיע אתכם, אך CSS Button Generator ייצר כפתורים עגולים ויפים בעזרת CSS בלבד.
  • kuler יספק לכם sets של 5 צבעים שמתאימים אחד לשני. לא עוד אתר בעיצוב גיאורגי (החליפו את המחוק בכל שם שנותן לכם קונוטציה לעיצוב גרוע).
  • Pattern Cooler ייצר לכם תמונות רקע ש\"מתחברות אחת לשנייה\" ויוצרות טאפט.

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

ולסיום – IDE  מתקדם לפיתוח ווב
כלי פיתוח לווב, משום מה, תמיד היו מאותגרים. לרוב הם לא סיפקו הרבה יותר מ Syntax Highlighting ו Auto-Completion בסיסי מבוסס השפה. IDE מתוחכם יחסית ששינה את התמונה הוא Aptana והוא מסופק בחינם. עד לא מזמן, הוא כנראה היה ה IDE המתקדם ביותר לפיתוח Web.

Aptana הוא מורכב ומסורבל ופשוט לא מסודר נוח. מעולם לא חיבבתי אותו. העדפתי אפילו להשתמש ב++Notepad שהיה מוגבל אך זריז. עד שגיליתי את מה שמפתחי הווב המתוחכמים מכירים: IDE מבית IntelliJ בשם WebStorm.

כמובן שיש פה עניין של טעם אישי, אך אם אתם מפתחים ווב בחצי משרה או יותר, ומוכנים להשקיע bare 100$ בשביל איכות החיים שלכם – כדאי לכם להוריד גרסת ניסיון של Web Storm. אני לא אפרט את רשימת הפ\'יצרים (תמיכה ב SASS, LESS, CoffeeScript, Node.js השלמה אוטומטית חכמה, Refactoring ו Debugger חזק – אופס, פירטתי). הדבר שאני הכי אוהב ב IDE הזה שהוא מרגיש לי טבעי ו\"זורם איתי\"  בוייב הנכון. לא מקשה עלי, כמו Aptana. שלא תאמרו שלא ידעתם.

[א] לא לבלבל עם SaaS – Software as a Service.

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

[ג] בעצם V8.

טרנדים חמים בתחום ה Web וה Mobile – חלק 2

בהמשך לפוסט הקודם בסדרה, טרנדים חמים בתחום ה Web וה Mobile – חלק 1, הנה עוד טרנדים חמים בתחום הווב והמובייל:

Unit Testing ל JavaScript
אין פה משהו חדש לגמרי: מי שבאמת רצה, יכול היה לכתוב unit tests לקוד ה JavaScript עוד מזמן. עד לא מזמן Unit Tests היו נפוצים בעיקר בצד השרת ומעט מאוד בצד הלקוח. מה שהשתנה הוא הפיתוח הרב שנעשה למובייל, שעבורו פתרונות כמו GWT, פלאש או IceFaces – כבר אינם טובים מספיק.

העיקרון דומה למדי ל Unit Tests בצד השרת, מלבד העובדה שהשונות בין סביבות הריצה היא רעש לא קטן. בעוד שלג\'אווה יש JVMים רבים, יכולתי בתור מפתח לבדוק את הקוד שלי על JVM של Windows (רץ ב IDE וב CI) ולהניח שהוא ירוץ בצורה זהה על שאר הפלטרפורמות הנפוצות. את רוב ה JVMים הקיימים מייצרת סאן אורקל והם דומים למדי. במצב נדיר בו הקוד שלכם אמור לרוץ, נאמר, על Z/OS (מערכת הפעלה של יבמ למיינפריים) אזי עליכם להתמודד עם JVM של יצרן אחר (יבמ) וייתכנו שונויות. אני נתקלתי פעם אחת בחיי בקוד שמתנהג אחרת (סביב SoftReferences) בין JVM של סאן ל JVM של יבמ.

אם ניקח את Rhino (מנוע ה JavaScript שמגיע עם ג\'אווה) ונוסיף את Env.js, ספרייה טוב של ג\'ון ריזלינג (היוצר של jQuery) שמדמה את ה DOM של הדפדפן, נוכל לבצע Unit Testing שיבדקו בצורה דיי טובה את הריצה של הקוד שלנו ע\"פ התקנים הרלוונטים, ובצורה בינונית למדי את הריצה של הקוד על דפדפנים אמיתיים, בגרסאותיהם השונות.

ישנם 4 מנועי ג\'אווה סקריפט שמשולבים בדפדפנים נפוצים: V8 של גוגל, Spider Monkey של מוזילה, Chakra של מייקרוסופט ו SquirrelFish שרץ בספארי.
כל אלה שונים במידה מורגשת מ Rhino, ושונים גם אחד מהשני. Charka, הוא מנוע חדש שנכתב עבור IE9 והוא שונה מהמנוע של גרסאות קודמות (JScript Engine) שעדיין זמין על IE9 ב Compatibility Mode [א].

מה הפתרון?
ניתן להריץ כלי אוטומציה כמו Selenium על דפדפנים שונים כחלק מתהליך ה CI. רוצים להתקין מספר דפדפנים (בעיקר IE) על מחשב אחד? Spoon.Net הוא שירות מצויין שיעשה עבורכם את העבודה עבור תשלום של 60$ בשנה.
כלי אוטומציה פשוט ומגניב יותר שעושה את אותו הדבר הוא JSTestDriver. הוא \"משתלט\" על ה process של הדפדפן ולכן אמור להיות מהיר ואמין יותר מ Selenium שמתבסס על ה UI.
עבור מובייל, יש כאלו שבודקים רק על WebkitJSCore שכנראה מספיק דומה לV8 ברוב המקרים, וניתן להריץ אותו ללא הרצה של דפדפן. כלומר, הרבה יותר מהר. חפשו בגוגל \"Webkit headless\" כדי למצוא מגוון של פתרונות.

הנה עוד כמה ספריות שיעשו את פיתוח ה Unit Tests שלכם לקלים ומהנים יותר:
Jasmine – סביבת unit testing ו BDD, כנראה הנפוצה מכולן.
QUnit היא אלטרנטיבה מבית היוצר של ג\'ון ריזלינג. יש כאלו שאומרים שהיא קצת יותר טובה, אך הקהילה שלה קטנה יותר והשוני בקוד של הבדיקות הוא דיי זניח, לדעתי.
Mocha (מבטאים כמו \"מוקה\") היא ספריית בדיקות נחשבת, עם הרבה פלאג-אינים ותמיכה ב Node (פרטים בהמשך).

לסיום, הנה כלי בשם Frank שעושה קצת רעש. הוא משלב BDD עם שליטה קלה על מכשיר המובייל.
מומלץ לראות כחצי דקה, החל מ 3:30 דקות, מסרטון זה בכדי להבין במה מדובר. הבחור מבצע בדיקה פשוטה פעם אחת בצורה ידנית ופעם שנייה בצורה אוטומטית. יפה.

בדיקות תוכנה: \"אני לא סומך על עצמי, אבל אני סומך על פראנק\". מקור: youtube

Hybrid Web Container או בקיצור: PhoneGap
דילמה קשה ובסיסית של פיתוח למובייל הוא הבחירה בין פיתוח אפליקציית נייטיב (native) לבין אפליקצת ווב (כלומר HTML5 ו CSS).
לאפליקציית נייטיב יש יתרון ביצועים משמעותי, היא יכולה לגשת למצלמה ולרשימת אנשי הקשר ובעזרתה ניתן לייצר חווית משתמש \"יותר חלקה\".
אפליקציית ווב מאפשרת לכתוב קוד פעם אחת ולהשתמש בו, עם התאמות קלות, במכשירים שונים. גם ההתאמה למסך בגודל שונה (טאבלט, Note) היא קלה יותר. אין API שיישבר בין גרסאות של מערכות הפעלה.

ישנה אופציה שלישית דיי פופולרית – ה Hybrid Web Container.

איך לצרוך PhoneGap – ה HWC הנפוץ. מקור: האתר של phoneGap

הרעיון הוא דיי פשוט: כתבו אפליקציית HTML רגילה. ה Hybrid Web Container, או בקיצור HWC, יספק לכם אפליקציית נייטיב שכל מה שהיא עושה היא לפתוח מעין iFrame ענק על על המסך שמפנה לכתובת האפליקציה שלכם.
מה היתרון בכזה תרגיל?

  • אתם יכולים להפיץ את האפליקציה שלכם בעזרת ה AppStore או Google Play – היכן שמשתמשים בד\"כ מחפשים אחר אפליקציות.
  • ה HWC יחשוף יכולות native של המכשיר שלא זמינים ב HTML5 כ JavaScript API: מצלמה, אנשי קשר, גישה לקבצים מקומיים, notification (לדוגמה הפעלת רטט של המכשיר, או push messages) וכו\'. הערה: כותבי HTML5 עובדים כדי לאפשר להשתמש בשירותים אלו מאפליקציות ווב – אך הפער בין המצוי לרצוי הוא עוד גדול.
  • ה HWC יאפשר לכם לכתוב custom native code (נניח שדורש חישוב מאוד יעיל) ולחשוף אותו ב JavaScript עבור החלק ה HTML-י של האפליקציה שלכם.
האם HWC הוא הטוב משני העולמות? הפתרון המושלם שייתר את שתי הגישות האחרות (נייטיב וווב)? החומר השיווקי של מפתחי ה HWC יטען שכן, אך נראה שזו תשובה לא מדויקת.
HWC מביא איתו גם חסרונות מ-2 העולמות הקיימים, למשל:
  • חווית המשתמש לא תהיה חלקה ונקיה כמו באפליקציית נייטיב (עדיין יש דפדפן).
  • התאימות לכל המכשירים לא תהיה טובה כמו אפליקציית HTML.
  • אם תעשו שינוי בnative קוד תצטרכו לשחרר גרסה חדשה דרך ה AppStore, דבר שלוקח זמן אצל אפל אפילו אם מדובר ב\"תיקון אבטחה קריטי\". צד ה HTML – מצד שני, יצטרך לדעת להתמודד עם הגרסאות השונות של ה Container מכיוון שאין לכם שום דרך להכריח את המשתמשים לשדרג את ה Container.
בקיצור, HWC היא אופציה שלישית ואטרקטיבית – אך אינה בהכרח אופציה נעלה. היא פשוט עוד אופציה אפשרית שכדאי לשקול.

MVC Frameworks for JavaScript
מכירים Frameworks מבוססי תבנית העיצוב MVC – Model View Controller? אני מדבר על Struts, JSF או ASP.NET MVC? אני מניח שכן.
ובכן – תשכחו מכל מה שידעתם. אנחנו נדבר על MVC ל JavaScript.
לא, לא בגלל שMVC התגלה לפתע כלא-מוצלח. להיפך. בגלל שמה שנקרא כיום \"MVC Framework for JavaScript\" – הוא פשוט לא MVC במובן שאתם מכירים. העקרונות דומים, אך הצורך העיקרי איננו עוד בשליטה בניווט בין דפים. לדקדקנים: מדובר יותר ב MVP או MVVC – תבניות עיצוב שמתארות בצורה מדוייקת יותר את מה ש Frameworks אלו מספקים. בקיצור ניתן לומר שמדובר ב *MV, שיש בהם אלמנטים דומים ל MVC.
MVC קלאסי מבוסס על 2 עקרונות מרכזיים:
  • הראשי: הפרדה בין UI ל Business Logic, או מה שנקרא View ל Model. זו הפרדה חשובה מאז ומעולם. הקשר בין ה View ל Model יהיה לרוב ע\"י Value Objects בלבד, מה שנקרא בג\'אווה Beans.
  • המשני: העברת פעולות ה navigation בין הדפים למקום מרכזי (ה controller). במקום שכל דף יכיר את הדפים אליהם אפשר לנווט ממנו – הוא מכיר רק פעולות, וגורם שלישי מרכז את הפענוח וההפניה ליעד האמיתי. לעתים קרובות בעזרת קומפיגורציה שאפשר לשנות ללא כתיבת קוד או תלוית הקשר, משתמש למשל.
ה Framework הנפוץ ביותר ה Backbone.js. זהו \"*MV של JavaScript\" ללא Controller שמבצע ניווט בין דפי ווב. גם ההפרדה בין UI ל Business Logic היא given – אך היא לא העיקר. היכולת העיקרית שלו היא לסייע בכתיבת אפליקציות \"דף אחד\" עם אלמנטים דינמיים מתחלפים.
חשבו לדוגמה על Gmail: מעבר בין קריאת הודעה אחת לשנייה – מביאה את המידע מהשרת באופן אסינכרוני (Ajax) והדף מתעדכן ללא ריצוד. יצירת הודעה חדשה או שינוי הגדרות הם שינויים  חזותיים יותר משמעותיים – אך מתנהלים באופן דומה.
אם פיתחתם אפליקציות ווב שכאלה בעבר, קרוב לוודאי שפיתחתם תשתית משלכם לבצע את המטלות. אם הקפדתם – אז ייתכן שיצרתם Renderers שונים שמקבלים מבנה נתונים (שהגיע מהשרת) ויודעים לצייר אותו במקום הנכון.
מסתבר שחווית השימוש באפליקציית מובייל דומה הרבה יותר ל\"אפליקציית דף אחד\" מאשר לאתר אינטרנט בו מנווטים בין דפים – ולכן הצורך והפופולריות של Frameworks מסוג זה.

Backbone.js, למרות השם ה\"כבד\", הוא ספרייה רזה בת כ 700 שורות קוד (אם לא סופרים הערות). אפשר ללמוד אותו על בוריו בשעה-שעתיים והוא מתאים למכשירי מובייל. אם דיברנו בפוסט הקודם על Micro JS Frameworks- הרי גרסת ה minified שלו היא 16K בלבד. 

ישנם Frameworks רבים, אך ללא ספק הנפוץ שבהם הוא Backbone.js. בעולם ה .NET נפוץ יותר Knockout.js שמקדם את תבנית MVVP, שלמיטב ידיעתי, מוכרת בעיקר מפירסומים של מייקוסופט. 
עוד 2 ספריות נפוצות הן Spine.js ו Ember.js.

Node.js
חשבתם פעם בכמה שפות וטכנולוגיות מפתחי Web צריכים להתמחות? מצד אחד HTML CSS ו JavaScript, מצד שני לרוב האפליקציות זקוקים לשרת ואז צריך לכתוב ב Java, Ruby או #C. פעמים רבות, מפתחי הווב חזקים יותר בצד הלקוח ו\"קצת מחפפים\" בצד השרת. \"לו רק ניתן היה לפתח רק ב JavaScript…\".
מפתחי צד השרת (כמוני) נוטים לתעב ג\'אווהסקריפט ומחפשים כל מיני דרכים להימנע ממנו (פלאש, GWT ועוד).

האם אפשר להריץ Groovy (וריאנט של ג\'אווה שאני אוהב) בדפדפן? – בחלומות הלילה.
האם אפשר להריץ JavaScript בצד השרת? כן. קיימים מנועי ג\'אווהסקריפט שרצים בצד השרת כמו Rhino או V8. הרשו לי להציג את קפיצת המדרגה בצד זה: Node.js.

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

אבל בכל זאת – זה קצת גימיק, לא? JavaScript עם Interpreter הוא קצת איטי. ובכלל להשוות שרת מורכב כמו Apache Geronimo לאיזו ספריית JavaScript? זה נשמע קצת לא רציני.
אז מתי אני ארצה להשתמש בפיתוח ג\'אווהסקריפט לצד השרת? עבור בדיקות? עבור Prototyping?

האמת המפתיעה הוא ש Node.js הוא מהיר. מאוד מהיר.
בואו נתחיל בלהסביר ממה Node.js מורכב:
Node.js הוא (הגדרה שלי) שרת ווב שכתוב בג\'אווהסקריפט. הוא רץ על V8 – מנוע הג\'אווהסקריפט המהיר של גוגל (שבעצם מקפמל JIT את הג\'אווהסקריפט לקוד מכונה). בנוסף יש ספריות שונות המשלימות יכולות צד שרת שחסרות  בג\'אווהסקריפט (גישה לקבצים, תקשורת וכו\') וספריית ניהול חבילות בשם npm (המקבילה ל Ruby Gems או Python Eggs). הכתיבה ב Node היא דיי low level, ויש להרכיב לבד את הודעות ה HTTP, אך אין מניעה להשתמש בספריות שיאפשרו רמת אבסטרקציה גבוהה יותר.

Node מול אפאצ\'י – השרת המהיר של הווב. מקור: http://blog.raducojocaru.com

בעוד שרתי ווב מלפני עשור (כמו תקן ה Servlets של ג\'אווה) תוכננו בראיה של הגשת דפי HTML גדולים, Node נכתב בראייה של מענה להודעות קטנות (קריאות Ajax או WebSockets), כאשר שרת HTTP מצומד מגיש את קבצי ה JavaScript או CSS ובניית ה markup נעשית בצד הלקוח. התוצאה היא טיפול בעיקר בפעולות IO (בסיס נתונים, קבצים, רשת) קצרות. כל פעולות ה I/O ב Node הן אסינכרוניות ומבוססות callbacks ברגע שפעולת ה IO הסתיימה.

למטרה זו Node מהיר בצורה ניכרת מרוב השרתים שאנחנו מכירים היום (IIS, Tomcat וכו\') כאשר הוא צורך קצת יותר זיכרון ו CPU (יש Overhead לכל המקביליות הזו) אך הוא יכול לטפל במספר רב יותר של קריאות. בדיקות מסוימות מראות טיפול בפי 10 בקשות במקביל משרת Apache + PHP. המספרים המדויקים כמובן תלויים בתסריט המדויק – אך יש הרבה פוטנציאל. במצבים של גישות ארוכות שחסומות ב CPU – צפו לתוצאות משמעותית פחות מרשימות. כמו כן שימו לב שכדאי להימנע מקוד סינכרוני ולהתאים את גודל ה Connection Pool על מנת להגיע לתוצאות הרצויות על גבי Node.

את אותה ארכיטקטורה אסינכרונית ניתן כמובן לשחזר בשפות שונות. תוכלו למצוא אותה בשרת Webbit שכתוב בג\'אווה, למשל.

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

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

[א] המצב ב JavaScript הוא עוד טוב. אם אתם מתעסקים בDOM ותומכים ב IE, בקרוב תצטרכו לבדוק את הקוד שלכם על 72 גרסאות שונות.
על Android תצטרכו להתמודד עם Chrome (החל מ Android 4 ועדיין לא דפדפן ברירת המחדל) או Android Browser – דפדפן מעפאן לגמרי שזמין בכל הגרסאות המוקדמות יותר של אנדרואיד. בנוסף יש לא מעט דיווחים שחומרה שונה של אנדרואיד גורמת לדפדפן ה Android Browser להתנהג קצת אחרת – דבר שלא אימתתי בעצמי.