Monkey patching

טכניקה פשוטה (ומסוכנת) להוספת תכונות לאובייקטים קיימים בג'אווהסקריפט שכדאי להכיר

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

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

const myArray = ['avi', 'itzik', 'yaki'];

console.log(myArray.endsWith('yaki')); // true

console.log(myArray.endsWith('sarah')); // false

אם תריצו את הקוד הזה, תקבלו שגיאה מסוג:
Uncaught TypeError: myArray.endsWith is not a function

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

const Utils = {
  endsWith: (arr, arg) => arr[arr.length - 1] === arg ? true : false,
}

const myArray = ['avi', 'itzik', 'yaki'];

console.log(Utils.endsWith(myArray, 'yaki')); // true;

אבל זה עלול להיות מעיק ובעייתי. צריך לזכור שיש את ה-Utils וכל מי שכתב קוד מורכב יודע שספריות העזר האלו נוטות להתנפח. זה הדבר הכדאי לעשות בסופו של דבר, אבל יש דרך נוספת שנקראת Monkey Patching – לשנות את הקוד בזמן הריצה או יותר נכון, בג'אווהסקריפט, לשנות את הפרוטוטייפ של האובייקט בזמן אמת. בג'אווהסקריפט יש לנו את prototype, שמהווה את הבסיס של השפה. לכל אובייקט ואובייקט אפשר לגשת ולשנות את המתודות שלו באמצעות prototype. אני יכול להעניק לסוג המידע הפרימיטיבי Array את המתודה endsWith כך שתהיה זמינה במערך.

Array.prototype.endsWith = function(arg) {
  return this[this.length - 1] === arg ? true : false;
}

const myArray = ['avi', 'itzik', 'yaki'];

console.log(myArray.endsWith('yaki')); // true

console.log(myArray.endsWith('sarah')); // false

See the Pen monkey patching JavaScript array by Ran Bar-Zik (@barzik-the-vuer) on CodePen.

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

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

globalThis.console.log = (arg) => globalThis.console.error(arg);

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

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

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

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

פוסטים נוספים שכדאי לקרוא

פתרונות ומאמרים על פיתוח אינטרנט

יישום של nonce על מנת להגן מפני התקפות injection

בפוסט הקודם הסברתי על hash עם CSP על משאבי inline – שזה נחמד ומעולה אבל פחות ישים בעולם האמיתי שבו בדרך כלל התוכן ה-inline (בין

מיקרו בקרים

בית חכם עם ESPHome ו Home Assistant

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

גלילה לראש העמוד