פיצ׳רים ב es6/7/8 שאי אפשר בלעדיהם

נדמה שרק אתמול אנשים עוד עשו לי פרצוף כשלימדתי את document.querySelector באיזה קורס כי הוא לא נתמך באקספלורר 7, ואילו היום כולם רק עושים תחרות מי יכתוב JavaScript יותר חדשני.

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

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

הגדרות משתנים: let ו const

שתי המילים הראשונות שמופיעות בכל קטע קוד חדש שאני כותב הן let ו const. שתיהן מחליפות את var עם הבדל קטן ביניהן. אבל קודם - למה צריך להחליף את var?

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

function foo() {
  for (var i=0; i < 10; i++) {
    // nothing to see here... move on
  }
  console.log(i);
}

foo();

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

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

function foo() {
  for (let i=0; i < 10; i++) {
    // nothing to see here... move on
  }
  console.log(i);
}

foo();

בדיוק כמו שהיה קורה ב Java ובשפות רבות אחרות.

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

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

פונקציות חץ

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

function Foo() {
  this.value = 10;
}

Foo.prototype.printValue = function() {
  console.log('Before. value = ', this.value);
  setTimeout(function() {
    console.log('After. value = ' + this.value);
  }, 10)
};

const f = new Foo();
f.printValue();

מדפיס את הפלט:

Before. value =  10
After. value = undefined

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

function Foo() {
  this.value = 10;
}

Foo.prototype.printValue = function() {
  var that = this;
  console.log('Before. value = ', that.value);
  setTimeout(function() {
    console.log('After. value = ' + that.value);
  }, 10)
};

const f = new Foo();
f.printValue();

וזה כבר מדפיס פעמיים את הערך 10 אבל עדיין מבלבל.

אחרי זה שידרגנו ל ES5 ולמדנו להשתמש ב bind:

function Foo() {
  this.value = 10;
}

Foo.prototype.printValue = function() {
  console.log('Before. value = ', this.value);
  setTimeout(function() {
    console.log('After. value = ' + this.value);
  }.bind(this), 10)
};

const f = new Foo();
f.printValue();

bind אוטומטי

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

function Foo() {
  this.value = 10;
}

Foo.prototype.printValue = function() {
  console.log('Before. value = ', this.value);
  setTimeout(() => {
    console.log('After. value = ' + this.value);
  }, 10)
};

const f = new Foo();
f.printValue();

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

פונקציות של שורה אחת

קיצור דרך שני של פונקציית החץ מופיע עבור פונקציות של שורה אחת. בפונקציות כאלה המילה return יכולה להפריע בעין. נתבונן בקוד:

const arr = [10, 20, 25, 27, 30];
const odd = arr.filter(function(item) {
  return item % 2 !== 0;
});

console.log(odd);

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

const arr = [10, 20, 25, 27, 30];
const odd  = arr.filter((item) => item % 2 !== 0);
const even = arr.filter(item => item % 2 === 0);

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

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

שיערוך משתנים במחרוזות

אני חושב שזה הפיצ׳ר שהכי חיכיתי לו ב JavaScript מאז שהתחלתי לכתוב ל Web. ב ES5 לכתוב מחרוזת שיש בה מספר שורות וחלקה מורכבת מתוכן של משתנים היה סיוט. ואז קיבלנו את ה backtick ב ES6 והכל השתנה. פתאום אפשר לכתוב קוד כזה:

const name = 'ynon';
const text = `hello ${name}`;

ומכאן המרחק קצר ליצירת טמפלייטס ב JavaScript בלי ספריות:

const name = 'ynon';
const text = `hello ${name}`;

const data = [
  { href: 'https://www.tocode.co.il', text: 'ToCode Home' },
  { href: 'https://www.ynonperek.com', text: 'Ynon Perek English Blog' }
];

const div = document.querySelector('#links-div');

div.innerHTML = `
  <h2>Cool Links</h2>
  <ul>
    ${data.map(item => (
      <li><a href='${item.href}'>`${item.text}`</a></li>
    ))}
  </ul>
`;

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

המילה החדשה class

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

function Dog(name, age) {
  this.name = name;
  this.age  = age;
}

Dog.prototype.bark = function() {
  console.log(`Wuff Wuff, I'm ${this.name}!`);
};

Dog.prototype.growUp = function() {
  this.age += 1;
};

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

function RoboticDog(name) {
  Dog.call(this, name, 0);
  this.name = name;
}

RoboticDog.prototype = Object.create(Dog.prototype);

RoboticDog.prototype.growUp = function() {
  console.log('Robots never age');
};

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

class Dog {
  constructor(name, age) {
    this.name = name;
    this.age  = age;
  }

  bark() {
    console.log(`Wuff Wuff, I'm ${this.name}!`);
  }

  growUp() {
    this.age += 1;
  }
}

class RoboticDog extends Dog {
  constructor(name) {
    super(name, 0);
    this.name = name;
  }

  growUp() {
    console.log('Robots never age');
  }
}


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