במאמר הזה אני מסביר על 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, אני נמנע מכך. אבל חשוב להכיר את המונח.
8 תגובות
תודה. איך בודקים שהמתודה קיימת?
you don't
אפשר לבדוק כך:
if (arr.endsWith) {
// it's true so do you stuff
}
That's why you shouldn't use monkey patcking and use the symbol which is exactly what it was invented for .
This way , you don't touch any other proto methods.
see here :
https://jsbin.com/sovusecixe/1/edit?html,js,console
const symbol = Symbol('endsWith');
Array.prototype[symbol] = function(arg) {
return this[this.length – 1] === arg ? true : false;
}
const myArray = ['avi', 'itzik', 'yaki'];
console.log(myArray[symbol]('yaki')); // true
console.log(myArray[symbol]('sarah')); // false
Sorry this is the right jsbin
https://jsbin.com/sovusecixe/2/edit?html,js,console
למה בדוגמת הקוד יש גם === וגם לאחר מכן ?true:flase? הערך שמחזיר === הוא בעצמו בוליאני
חסר הסבר למה לעזאזל זה נקרא monkey patching ולא cute cat patching.
רן, תסתכל בבקשה במייל שלך המקושר לאתר. תודה.