במאמר הקודם סיפרתי קצת על מוטציה בג'אווהסקריפט. ואיך בטעות אנו יכולים לשנות אובייקטים. טוב, את זה אנו מכירים – כאשר אני מבצע העתקה ואז שינוי, אובייקט האב משתנה לי. למה? כי אין ממש העתקה, יש reference – התוצאה (נרצה או לא נרצה) נקראת בג'אווהסקריפט מוטציה. הנה דוגמה קצרה רק כדי להזכר:
const object1 = {
property1: 42
};
const object2 = object1;
object2.property1 = 33;
console.log(object1.property1); // 33
זה באמת עלול להבעית קצת. אבל ככה השפה עובדת. מצד שני, זה עלול להעיק. פתרון אחד ספציפי הוא תמיד לבצע deep clone (העתקה) או assign כדי לבצע העתקה של האובייקט החדש והמשך עבודה.
const object1 = {
property1: 42
};
const object2 = Object.assign({}, object1);
object2.property1 = 33;
console.log(object1.property1); // 42
יש לזה כמובן את הבעיות שלו, אבל לא נכנס לזה. אם יצרנו אובייקט שאנו לא מעונינים שיגעו בו או ישנו אותו, אנו יכולים לבצע הקפאה שלו עם Object.freeze – זה די קל, מלווה אותנו כבר הרבה שנים ונתמך על ידי כל הדפדפנים:
const object1 = {
property1: 42
};
Object.freeze(object1)
const object2 = object1;
object2.property1 = 33;
console.log(object1.property1); // 42
console.log(Object.isFrozen(object1)); // true
מה שה-freeze עושה הוא בעצם הקפאת כל התכונות המיידיות של האובייקט. אפשר לבדוק אם אובייקט קפוא באמצעות isFrozen. כדאי לשים לב שאם מדובר באובייקט מורכב (כלומר אובייקט שהתכונה שלו היא אובייקט אחר) – יהיה אפשר לבצע שינוי של האובייקט. בדיוק כמו ב-const.
והנה דוגמה נוספת עם מערך שאותו אני מקפיא. אפשר לראות איך המוטציה נבלמת.
const myArray = ['Value', 'Value2', 'Value3'];
Object.freeze(myArray);
function muteMeBaby(someArray) {
someArray[0] = 'newValue';
return someArray;
}
const newArray = muteMeBaby(myArray);
console.log(newArray); // ["newValue", "Value2", "Value3"]
console.log(myArray); // ["newValue", "Value2", "Value3"]
ניסיון של שינוי אובייקט שנמצא ב-freeze יניב שגיאה אם אנו נמצאים ב strict mode.
האמת היא שלא יצא לי לראות יותר מדי שימושים ב-freeze, ולפי דעתי זו טכניקה שאני לא הייתי שמח לראות אותה בקוד שלי מכמה סיבות. אבל ללא ספק טוב לראות וטוב להכיר.
דרך מעניינת נוספת לקבע אובייקטים היא seal. לא, אין הכוונה לזמר האיכותי (האיכותי!) משנות ה-90 אלא לטכניקה שבאמצעותה אני יכול לקבע את התכונות של האובייקט אבל לאפשר לשנות את הערך שלהן.
אני חושב שבמקרה הזה דוגמה טובה יותר מאלף מילים. יצרתי אובייקט ועשיתי לו seal. מהרגע הזה, אני יכול לשנות את התכונות שלו אבל אני לא יכול להוסיף לו חדשות.
const object1 = {
property1: 42
};
Object.seal(object1)
const object2 = object1;
object2.property1 = 33;
object2.property2 = 11;
console.log(object1.property1); // 33
console.log(object1.property2); // undefined
console.log(Object.isSealed(object1)); // true
console.log(object2.property2); // undefined
See the Pen Seal baby seal by Ran Bar-Zik (@barzik) on CodePen.
זה כמובן עובר לכל האובייקטים שיש להם רפרפנס אליו. זה לא עוצר את כל המוטציות אבל בהחלט יכול לאכוף ממשק. ושוב, זה נחמד מאוד להכיר – אבל אני לא חושב שיש לזה הרבה שימוש. בתכנות מונחה עצמים, הרבה פעמים אנו כן רוצים להתחייב לממשק מסוים. אבל ג'אווהסקריפט היא prototype based וניסיון להכניס לתוכה את ההגיון של ה-class יכול להיות בעייתי. כאמור טוב להכיר, שימושי בכמה מקרים – באופן אישי לא ראיתי שימוש מאוד נרחב.
3 תגובות
אחד השימושים הנחמדים שראיתי – הקפאת אובייקט שלא משתנה כדי לחסום את בדיקת הריאקטיביות של Vue.js ולשפר ביצועים באופן משמעותי, כפי שמופיע כאן:
https://vuedose.tips/tips/improve-performance-on-large-lists-in-vue-js
וכמובן תודה על עוד מאמר כיפי ומהנה
לא הכרתי את הפונקציה הזאת ונהנתי מאוד מהקריאה.
יש לך טעות קטנה בדוגמא עם המערך המוקפא. הפלט יהיה "Value" ולא "newValue" עבור התא הראשון בכל אחד מהמערכים
https://i.imgur.com/QUr8PEg.png
מאמר מעולה כתמיד
כמה נקודות חשובות
deep clone !== assign
בשביל לבצע העתקה של אובייקט ב-js באמצעות assign
נדרש לבצע deep assign אחרת זה יעתיק גם הרפרנס
שינוי של ערך באובייקט פנימי במועתק יגרור שינוי גם במקורי
בנוסף Object.freeze יקפיא לו רק את המקורי אלא גם את המועתק
ואמנם שינוי באחד לא ישפיע על השני אך גם לא על עצמו